Platzhalter finalisiert - Option |d und Option |x implementiert #77
|
|
@ -13,6 +13,12 @@ from . import body_metrics
|
|||
from . import body_extras
|
||||
from . import activity_metrics
|
||||
from . import activity_session_insights
|
||||
from . import schlaf_erholung
|
||||
from . import vitalwerte
|
||||
from . import profil_zeitraum
|
||||
from . import phase_0b_meta_scores
|
||||
from . import phase_0b_ziele_fokus
|
||||
from . import korrelationen
|
||||
|
||||
__all__ = [
|
||||
'nutrition_part_a',
|
||||
|
|
@ -23,4 +29,10 @@ __all__ = [
|
|||
'body_extras',
|
||||
'activity_metrics',
|
||||
'activity_session_insights',
|
||||
'schlaf_erholung',
|
||||
'vitalwerte',
|
||||
'profil_zeitraum',
|
||||
'phase_0b_meta_scores',
|
||||
'phase_0b_ziele_fokus',
|
||||
'korrelationen',
|
||||
]
|
||||
|
|
|
|||
19
backend/placeholder_registrations/_evidence.py
Normal file
19
backend/placeholder_registrations/_evidence.py
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
"""Gemeinsames Evidence-Tagging für Registry-Einträge."""
|
||||
|
||||
from placeholder_registry import EvidenceType, PlaceholderMetadata
|
||||
|
||||
STANDARD_FIELDS = (
|
||||
"key", "category", "description", "resolver_module", "resolver_function",
|
||||
"data_layer_module", "data_layer_function", "source_tables", "semantic_contract",
|
||||
"unit", "time_window", "output_type", "placeholder_type", "format_hint",
|
||||
"example_output", "minimum_data_requirements", "confidence_logic",
|
||||
"missing_value_policy", "layer_1_decision", "layer_2a_decision",
|
||||
"layer_2b_reuse_possible", "architecture_alignment", "issue_53_alignment",
|
||||
)
|
||||
|
||||
|
||||
def tag_standard_evidence(meta: PlaceholderMetadata) -> None:
|
||||
for field in STANDARD_FIELDS:
|
||||
meta.set_evidence(field, EvidenceType.CODE_DERIVED)
|
||||
meta.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
|
||||
meta.set_evidence("known_limitations", EvidenceType.MIXED)
|
||||
96
backend/placeholder_registrations/korrelationen.py
Normal file
96
backend/placeholder_registrations/korrelationen.py
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
"""Registry: Korrelations- und Treiber-Metriken (Data Layer correlations)."""
|
||||
|
||||
from placeholder_registry import (
|
||||
PlaceholderMetadata,
|
||||
MissingValuePolicy,
|
||||
OutputType,
|
||||
PlaceholderType,
|
||||
register_placeholder,
|
||||
)
|
||||
from ._evidence import tag_standard_evidence
|
||||
|
||||
CAT = "Korrelationen"
|
||||
MVP = lambda reason, disp: MissingValuePolicy(
|
||||
available=False, value_raw=None, missing_reason=reason, legacy_display=disp
|
||||
)
|
||||
|
||||
|
||||
def register_korrelationen():
|
||||
for key, dl_fn, desc, tables, sem in [
|
||||
(
|
||||
"correlation_energy_weight_lag",
|
||||
"calculate_lag_correlation",
|
||||
"JSON: Lag-Korrelation Energiebilanz ↔ Gewicht",
|
||||
["nutrition_log", "weight_log"],
|
||||
"correlations.calculate_lag_correlation(pid, 'energy', 'weight')",
|
||||
),
|
||||
(
|
||||
"correlation_protein_lbm",
|
||||
"calculate_lag_correlation",
|
||||
"JSON: Lag-Korrelation Protein ↔ Magermasse",
|
||||
["nutrition_log", "weight_log", "caliper_log"],
|
||||
"correlations.calculate_lag_correlation(pid, 'protein', 'lbm')",
|
||||
),
|
||||
(
|
||||
"correlation_load_hrv",
|
||||
"calculate_lag_correlation",
|
||||
"JSON: Lag-Korrelation Trainingslast ↔ HRV",
|
||||
["activity_log", "vitals_baseline"],
|
||||
"correlations.calculate_lag_correlation(pid, 'training_load', 'hrv')",
|
||||
),
|
||||
(
|
||||
"correlation_load_rhr",
|
||||
"calculate_lag_correlation",
|
||||
"JSON: Lag-Korrelation Trainingslast ↔ Ruhepuls",
|
||||
["activity_log", "vitals_baseline"],
|
||||
"correlations.calculate_lag_correlation(pid, 'training_load', 'rhr')",
|
||||
),
|
||||
(
|
||||
"plateau_detected",
|
||||
"calculate_plateau_detected",
|
||||
"JSON: Platten-Erkennung (Gewicht/Körper)",
|
||||
["weight_log", "caliper_log"],
|
||||
"correlations.calculate_plateau_detected",
|
||||
),
|
||||
(
|
||||
"top_drivers",
|
||||
"calculate_top_drivers",
|
||||
"JSON: Top Treiber für Ziel-/Score-Variablen",
|
||||
["weight_log", "nutrition_log", "activity_log", "vitals_baseline", "sleep_log"],
|
||||
"correlations.calculate_top_drivers",
|
||||
),
|
||||
]:
|
||||
m = PlaceholderMetadata(
|
||||
key=key,
|
||||
category=CAT,
|
||||
description=desc,
|
||||
resolver_module="backend/placeholder_resolver.py",
|
||||
resolver_function="_safe_json",
|
||||
data_layer_module="backend/data_layer/correlations.py",
|
||||
data_layer_function=dl_fn,
|
||||
source_tables=tables,
|
||||
semantic_contract=sem,
|
||||
business_meaning="Strukturierte Korrelationsausgabe für KI",
|
||||
unit="JSON",
|
||||
time_window="funktionsintern",
|
||||
output_type=OutputType.JSON,
|
||||
placeholder_type=PlaceholderType.RAW_DATA,
|
||||
format_hint="JSON-String",
|
||||
example_output="{}",
|
||||
minimum_data_requirements="Ausreichend gekoppelte Zeitreihen",
|
||||
quality_filter_policy=None,
|
||||
confidence_logic="Wie correlations.*",
|
||||
missing_value_policy=MVP("insufficient_data", "{}"),
|
||||
known_limitations="Bei wenigen Daten leer oder wenig robust",
|
||||
layer_1_decision=f"correlations.{dl_fn}",
|
||||
layer_2a_decision="_safe_json",
|
||||
layer_2b_reuse_possible=True,
|
||||
architecture_alignment="Phase 0c",
|
||||
issue_53_alignment="Layer 1",
|
||||
evidence={},
|
||||
)
|
||||
tag_standard_evidence(m)
|
||||
register_placeholder(m)
|
||||
|
||||
|
||||
register_korrelationen()
|
||||
66
backend/placeholder_registrations/phase_0b_meta_scores.py
Normal file
66
backend/placeholder_registrations/phase_0b_meta_scores.py
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
"""Registry: Meta-Scores (Phase 0b) — Ziel-Fortschritt und Datenqualität."""
|
||||
|
||||
from placeholder_registry import (
|
||||
PlaceholderMetadata,
|
||||
MissingValuePolicy,
|
||||
OutputType,
|
||||
PlaceholderType,
|
||||
register_placeholder,
|
||||
)
|
||||
from ._evidence import tag_standard_evidence
|
||||
|
||||
CAT = "Scores (Phase 0b)"
|
||||
MVP = lambda reason, disp: MissingValuePolicy(
|
||||
available=False, value_raw=None, missing_reason=reason, legacy_display=disp
|
||||
)
|
||||
|
||||
|
||||
def register_phase_0b_meta_scores():
|
||||
for key, dl_fn, desc, unit in [
|
||||
(
|
||||
"goal_progress_score",
|
||||
"calculate_goal_progress_score",
|
||||
"Aggregierter Ziel-Fortschritt 0–100",
|
||||
"0–100",
|
||||
),
|
||||
(
|
||||
"data_quality_score",
|
||||
"calculate_data_quality_score",
|
||||
"Datenqualitäts-Score 0–100",
|
||||
"0–100",
|
||||
),
|
||||
]:
|
||||
m = PlaceholderMetadata(
|
||||
key=key,
|
||||
category=CAT,
|
||||
description=desc,
|
||||
resolver_module="backend/placeholder_resolver.py",
|
||||
resolver_function="_safe_int",
|
||||
data_layer_module="backend/data_layer/scores.py",
|
||||
data_layer_function=dl_fn,
|
||||
source_tables=["goals", "weight_log", "nutrition_log", "activity_log", "profiles"],
|
||||
semantic_contract=f"scores.{dl_fn} (siehe Data Layer).",
|
||||
business_meaning="Meta-KPI für Prompt-Gewichtung",
|
||||
unit=unit,
|
||||
time_window="composite",
|
||||
output_type=OutputType.NUMERIC,
|
||||
placeholder_type=PlaceholderType.SCORE,
|
||||
format_hint="Ganzzahl als String",
|
||||
example_output="72",
|
||||
minimum_data_requirements="Abhängig von Score-Implementierung",
|
||||
quality_filter_policy=None,
|
||||
confidence_logic="Wie calculate_* in scores.py",
|
||||
missing_value_policy=MVP("insufficient_data", "nicht verfügbar"),
|
||||
known_limitations="Bei dünnen Daten weniger aussagekräftig",
|
||||
layer_1_decision=f"scores.{dl_fn}",
|
||||
layer_2a_decision="_safe_int",
|
||||
layer_2b_reuse_possible=True,
|
||||
architecture_alignment="Phase 0b",
|
||||
issue_53_alignment="Layer 1",
|
||||
evidence={},
|
||||
)
|
||||
tag_standard_evidence(m)
|
||||
register_placeholder(m)
|
||||
|
||||
|
||||
register_phase_0b_meta_scores()
|
||||
392
backend/placeholder_registrations/phase_0b_ziele_fokus.py
Normal file
392
backend/placeholder_registrations/phase_0b_ziele_fokus.py
Normal file
|
|
@ -0,0 +1,392 @@
|
|||
"""Registry: Ziele, Fokusbereiche, Kategorie-Scores und formatierte Listen (Phase 0b)."""
|
||||
|
||||
from placeholder_registry import (
|
||||
PlaceholderMetadata,
|
||||
MissingValuePolicy,
|
||||
OutputType,
|
||||
PlaceholderType,
|
||||
register_placeholder,
|
||||
)
|
||||
from ._evidence import tag_standard_evidence
|
||||
|
||||
CAT = "Ziele & Fokus (Phase 0b)"
|
||||
MVP = lambda reason, disp: MissingValuePolicy(
|
||||
available=False, value_raw=None, missing_reason=reason, legacy_display=disp
|
||||
)
|
||||
|
||||
|
||||
def register_phase_0b_ziele_fokus():
|
||||
# Top-Ziel / Top-Fokusbereich
|
||||
m = PlaceholderMetadata(
|
||||
key="top_goal_name",
|
||||
category=CAT,
|
||||
description="Name/Typ des höchstpriorisierten Ziels",
|
||||
resolver_module="backend/placeholder_resolver.py",
|
||||
resolver_function="_safe_str",
|
||||
data_layer_module="backend/data_layer/scores.py",
|
||||
data_layer_function="get_top_priority_goal",
|
||||
source_tables=["goals"],
|
||||
semantic_contract="Feld name oder goal_type aus get_top_priority_goal",
|
||||
business_meaning="Priorisierung für KI-Empfehlungen",
|
||||
unit="text",
|
||||
time_window="aktuell",
|
||||
output_type=OutputType.STRING,
|
||||
placeholder_type=PlaceholderType.INTERPRETED,
|
||||
format_hint="Kurztext",
|
||||
example_output="Gewicht 80kg",
|
||||
minimum_data_requirements="Mindestens ein aktives Ziel",
|
||||
quality_filter_policy=None,
|
||||
confidence_logic="scores.get_top_priority_goal",
|
||||
missing_value_policy=MVP("insufficient_data", "nicht verfügbar"),
|
||||
known_limitations=None,
|
||||
layer_1_decision="scores.get_top_priority_goal",
|
||||
layer_2a_decision="_safe_str('top_goal_name')",
|
||||
layer_2b_reuse_possible=False,
|
||||
architecture_alignment="Phase 0b",
|
||||
issue_53_alignment="Layer 1",
|
||||
evidence={},
|
||||
)
|
||||
tag_standard_evidence(m)
|
||||
register_placeholder(m)
|
||||
|
||||
m = PlaceholderMetadata(
|
||||
key="top_goal_progress_pct",
|
||||
category=CAT,
|
||||
description="Fortschritt Top-Ziel (%)",
|
||||
resolver_module="backend/placeholder_resolver.py",
|
||||
resolver_function="_safe_int",
|
||||
data_layer_module="backend/data_layer/scores.py",
|
||||
data_layer_function="get_top_priority_goal",
|
||||
source_tables=["goals"],
|
||||
semantic_contract="progress_pct aus get_top_priority_goal",
|
||||
business_meaning="Priorisierung für KI-Empfehlungen",
|
||||
unit="%",
|
||||
time_window="aktuell",
|
||||
output_type=OutputType.NUMERIC,
|
||||
placeholder_type=PlaceholderType.SCORE,
|
||||
format_hint="Ganzzahl",
|
||||
example_output="65",
|
||||
minimum_data_requirements="Mindestens ein aktives Ziel",
|
||||
quality_filter_policy=None,
|
||||
confidence_logic="scores.get_top_priority_goal",
|
||||
missing_value_policy=MVP("insufficient_data", "nicht verfügbar"),
|
||||
known_limitations=None,
|
||||
layer_1_decision="scores.get_top_priority_goal",
|
||||
layer_2a_decision="_safe_int('top_goal_progress_pct')",
|
||||
layer_2b_reuse_possible=False,
|
||||
architecture_alignment="Phase 0b",
|
||||
issue_53_alignment="Layer 1",
|
||||
evidence={},
|
||||
)
|
||||
tag_standard_evidence(m)
|
||||
register_placeholder(m)
|
||||
|
||||
m = PlaceholderMetadata(
|
||||
key="top_goal_status",
|
||||
category=CAT,
|
||||
description="Status-Label Top-Ziel",
|
||||
resolver_module="backend/placeholder_resolver.py",
|
||||
resolver_function="_safe_str",
|
||||
data_layer_module="backend/data_layer/scores.py",
|
||||
data_layer_function="get_top_priority_goal",
|
||||
source_tables=["goals"],
|
||||
semantic_contract="status aus get_top_priority_goal",
|
||||
business_meaning="Priorisierung für KI-Empfehlungen",
|
||||
unit="text",
|
||||
time_window="aktuell",
|
||||
output_type=OutputType.STRING,
|
||||
placeholder_type=PlaceholderType.INTERPRETED,
|
||||
format_hint="Kurztext",
|
||||
example_output="active",
|
||||
minimum_data_requirements="Mindestens ein aktives Ziel",
|
||||
quality_filter_policy=None,
|
||||
confidence_logic="scores.get_top_priority_goal",
|
||||
missing_value_policy=MVP("insufficient_data", "nicht verfügbar"),
|
||||
known_limitations=None,
|
||||
layer_1_decision="scores.get_top_priority_goal",
|
||||
layer_2a_decision="_safe_str('top_goal_status')",
|
||||
layer_2b_reuse_possible=False,
|
||||
architecture_alignment="Phase 0b",
|
||||
issue_53_alignment="Layer 1",
|
||||
evidence={},
|
||||
)
|
||||
tag_standard_evidence(m)
|
||||
register_placeholder(m)
|
||||
|
||||
m = PlaceholderMetadata(
|
||||
key="top_focus_area_name",
|
||||
category=CAT,
|
||||
description="Bezeichnung des gewichtet stärksten Fokusbereichs",
|
||||
resolver_module="backend/placeholder_resolver.py",
|
||||
resolver_function="_safe_str",
|
||||
data_layer_module="backend/data_layer/scores.py",
|
||||
data_layer_function="get_top_focus_area",
|
||||
source_tables=["user_focus_area_weights", "focus_area_definitions"],
|
||||
semantic_contract="label aus get_top_focus_area",
|
||||
business_meaning="Priorisierung für KI-Empfehlungen",
|
||||
unit="text",
|
||||
time_window="aktuell",
|
||||
output_type=OutputType.STRING,
|
||||
placeholder_type=PlaceholderType.INTERPRETED,
|
||||
format_hint="Kurztext",
|
||||
example_output="Kraft",
|
||||
minimum_data_requirements="Gewichtete Fokusbereiche",
|
||||
quality_filter_policy=None,
|
||||
confidence_logic="scores.get_top_focus_area",
|
||||
missing_value_policy=MVP("insufficient_data", "nicht verfügbar"),
|
||||
known_limitations=None,
|
||||
layer_1_decision="scores.get_top_focus_area",
|
||||
layer_2a_decision="_safe_str('top_focus_area_name')",
|
||||
layer_2b_reuse_possible=False,
|
||||
architecture_alignment="Phase 0b",
|
||||
issue_53_alignment="Layer 1",
|
||||
evidence={},
|
||||
)
|
||||
tag_standard_evidence(m)
|
||||
register_placeholder(m)
|
||||
|
||||
m = PlaceholderMetadata(
|
||||
key="top_focus_area_progress",
|
||||
category=CAT,
|
||||
description="Fortschritt Top-Fokusbereich (%)",
|
||||
resolver_module="backend/placeholder_resolver.py",
|
||||
resolver_function="_safe_int",
|
||||
data_layer_module="backend/data_layer/scores.py",
|
||||
data_layer_function="get_top_focus_area",
|
||||
source_tables=["user_focus_area_weights", "focus_area_definitions", "goals"],
|
||||
semantic_contract="progress aus get_top_focus_area",
|
||||
business_meaning="Priorisierung für KI-Empfehlungen",
|
||||
unit="%",
|
||||
time_window="aktuell",
|
||||
output_type=OutputType.NUMERIC,
|
||||
placeholder_type=PlaceholderType.SCORE,
|
||||
format_hint="Ganzzahl",
|
||||
example_output="58",
|
||||
minimum_data_requirements="Gewichtete Fokusbereiche",
|
||||
quality_filter_policy=None,
|
||||
confidence_logic="scores.get_top_focus_area",
|
||||
missing_value_policy=MVP("insufficient_data", "nicht verfügbar"),
|
||||
known_limitations=None,
|
||||
layer_1_decision="scores.get_top_focus_area",
|
||||
layer_2a_decision="_safe_int('top_focus_area_progress')",
|
||||
layer_2b_reuse_possible=False,
|
||||
architecture_alignment="Phase 0b",
|
||||
issue_53_alignment="Layer 1",
|
||||
evidence={},
|
||||
)
|
||||
tag_standard_evidence(m)
|
||||
register_placeholder(m)
|
||||
|
||||
# Kategorie Progress / Weight (7 Kategorien)
|
||||
for slug in (
|
||||
"körper",
|
||||
"ernährung",
|
||||
"aktivität",
|
||||
"recovery",
|
||||
"vitalwerte",
|
||||
"mental",
|
||||
"lebensstil",
|
||||
):
|
||||
key_p = f"focus_cat_{slug}_progress"
|
||||
key_w = f"focus_cat_{slug}_weight"
|
||||
m_p = PlaceholderMetadata(
|
||||
key=key_p,
|
||||
category=CAT,
|
||||
description=f"Aggregierter Fortschritt Kategorie „{slug}“ (%)",
|
||||
resolver_module="backend/placeholder_resolver.py",
|
||||
resolver_function="_safe_int",
|
||||
data_layer_module="backend/data_layer/scores.py",
|
||||
data_layer_function="calculate_category_progress",
|
||||
source_tables=["goals", "focus_area_definitions", "user_focus_area_weights"],
|
||||
semantic_contract=f"scores.calculate_category_progress(pid, '{slug}')",
|
||||
business_meaning="Focus-Area-Kategorie-Score",
|
||||
unit="%",
|
||||
time_window="aktuell",
|
||||
output_type=OutputType.NUMERIC,
|
||||
placeholder_type=PlaceholderType.SCORE,
|
||||
format_hint="Ganzzahl",
|
||||
example_output="55",
|
||||
minimum_data_requirements="Gewichtete Bereiche in Kategorie",
|
||||
quality_filter_policy=None,
|
||||
confidence_logic="scores.calculate_category_progress",
|
||||
missing_value_policy=MVP("insufficient_data", "nicht verfügbar"),
|
||||
known_limitations=None,
|
||||
layer_1_decision="scores.calculate_category_progress",
|
||||
layer_2a_decision="_safe_int",
|
||||
layer_2b_reuse_possible=True,
|
||||
architecture_alignment="Phase 0b",
|
||||
issue_53_alignment="Layer 1",
|
||||
evidence={},
|
||||
)
|
||||
tag_standard_evidence(m_p)
|
||||
register_placeholder(m_p)
|
||||
|
||||
m_w = PlaceholderMetadata(
|
||||
key=key_w,
|
||||
category=CAT,
|
||||
description=f"Nutzer-Gewichtung Kategorie „{slug}“ (Anteil 0–1)",
|
||||
resolver_module="backend/placeholder_resolver.py",
|
||||
resolver_function="_safe_float",
|
||||
data_layer_module="backend/data_layer/scores.py",
|
||||
data_layer_function="calculate_category_weight",
|
||||
source_tables=["user_focus_area_weights", "focus_area_definitions"],
|
||||
semantic_contract=f"scores.calculate_category_weight(pid, '{slug}')",
|
||||
business_meaning="Kategorie-Gewichtung im Fokusmodell",
|
||||
unit="0–1",
|
||||
time_window="aktuell",
|
||||
output_type=OutputType.NUMERIC,
|
||||
placeholder_type=PlaceholderType.INTERPRETED,
|
||||
format_hint="Dezimal",
|
||||
example_output="0.25",
|
||||
minimum_data_requirements="user_focus_area_weights",
|
||||
quality_filter_policy=None,
|
||||
confidence_logic="scores.calculate_category_weight",
|
||||
missing_value_policy=MVP("insufficient_data", "nicht verfügbar"),
|
||||
known_limitations=None,
|
||||
layer_1_decision="scores.calculate_category_weight",
|
||||
layer_2a_decision="_safe_float",
|
||||
layer_2b_reuse_possible=False,
|
||||
architecture_alignment="Phase 0b",
|
||||
issue_53_alignment="Layer 1",
|
||||
evidence={},
|
||||
)
|
||||
tag_standard_evidence(m_w)
|
||||
register_placeholder(m_w)
|
||||
|
||||
# Strukturierte Ziele / Fokus
|
||||
for key, res_fn, dl_mod, dl_fn, desc, out, ptype in [
|
||||
(
|
||||
"active_goals_json",
|
||||
"_safe_json",
|
||||
"backend/goal_utils.py",
|
||||
"get_active_goals",
|
||||
"Aktive Ziele als JSON",
|
||||
OutputType.JSON,
|
||||
PlaceholderType.RAW_DATA,
|
||||
),
|
||||
(
|
||||
"active_goals_md",
|
||||
"_safe_str",
|
||||
"backend/placeholder_resolver.py",
|
||||
"_format_goals_as_markdown",
|
||||
"Aktive Ziele als Markdown-Tabelle",
|
||||
OutputType.TEXT_SUMMARY,
|
||||
PlaceholderType.INTERPRETED,
|
||||
),
|
||||
(
|
||||
"focus_areas_weighted_json",
|
||||
"_safe_json",
|
||||
"backend/placeholder_resolver.py",
|
||||
"_get_focus_areas_weighted_json",
|
||||
"Gewichtete Fokusbereiche mit Namen (JSON)",
|
||||
OutputType.JSON,
|
||||
PlaceholderType.RAW_DATA,
|
||||
),
|
||||
(
|
||||
"focus_areas_weighted_md",
|
||||
"_safe_str",
|
||||
"backend/placeholder_resolver.py",
|
||||
"_format_focus_areas_as_markdown",
|
||||
"Gewichtete Fokusbereiche als Markdown",
|
||||
OutputType.TEXT_SUMMARY,
|
||||
PlaceholderType.INTERPRETED,
|
||||
),
|
||||
(
|
||||
"focus_area_weights_json",
|
||||
"_safe_json",
|
||||
"backend/data_layer/scores.py",
|
||||
"get_user_focus_weights",
|
||||
"Rohe Gewichtungen key→Anteil (JSON)",
|
||||
OutputType.JSON,
|
||||
PlaceholderType.RAW_DATA,
|
||||
),
|
||||
]:
|
||||
m = PlaceholderMetadata(
|
||||
key=key,
|
||||
category=CAT,
|
||||
description=desc,
|
||||
resolver_module="backend/placeholder_resolver.py",
|
||||
resolver_function=res_fn,
|
||||
data_layer_module=dl_mod,
|
||||
data_layer_function=dl_fn,
|
||||
source_tables=["goals", "focus_area_definitions", "user_focus_area_weights"],
|
||||
semantic_contract=f"{dl_fn} (siehe Modul {dl_mod})",
|
||||
business_meaning="Strukturierte Übersicht für Prompts",
|
||||
unit="JSON" if out == OutputType.JSON else "markdown",
|
||||
time_window="aktuell",
|
||||
output_type=out,
|
||||
placeholder_type=ptype,
|
||||
format_hint="String aus Resolver",
|
||||
example_output="[]" if out == OutputType.JSON else "—",
|
||||
minimum_data_requirements="Ziele bzw. Fokusgewichte",
|
||||
quality_filter_policy=None,
|
||||
confidence_logic="Resolver + goal_utils / scores",
|
||||
missing_value_policy=MVP("insufficient_data", "[]" if out == OutputType.JSON else "nicht verfügbar"),
|
||||
known_limitations=None,
|
||||
layer_1_decision=dl_fn,
|
||||
layer_2a_decision=res_fn,
|
||||
layer_2b_reuse_possible=False,
|
||||
architecture_alignment="Phase 0b",
|
||||
issue_53_alignment="Layer 1",
|
||||
evidence={},
|
||||
)
|
||||
tag_standard_evidence(m)
|
||||
register_placeholder(m)
|
||||
|
||||
for key, res_fn, dl_fn, desc, ex in [
|
||||
(
|
||||
"top_3_focus_areas",
|
||||
"_safe_str",
|
||||
"_format_top_focus_areas",
|
||||
"Top-3 Fokusbereiche als formatierter Text",
|
||||
"1. Kraft …",
|
||||
),
|
||||
(
|
||||
"top_3_goals_behind_schedule",
|
||||
"_safe_str",
|
||||
"_format_goals_behind",
|
||||
"Bis zu drei Ziele hinter Zeitplan",
|
||||
"—",
|
||||
),
|
||||
(
|
||||
"top_3_goals_on_track",
|
||||
"_safe_str",
|
||||
"_format_goals_on_track",
|
||||
"Bis zu drei Ziele im Plan",
|
||||
"—",
|
||||
),
|
||||
]:
|
||||
m = PlaceholderMetadata(
|
||||
key=key,
|
||||
category=CAT,
|
||||
description=desc,
|
||||
resolver_module="backend/placeholder_resolver.py",
|
||||
resolver_function=res_fn,
|
||||
data_layer_module="backend/goal_utils.py",
|
||||
data_layer_function="get_active_goals",
|
||||
source_tables=["goals", "focus_area_definitions"],
|
||||
semantic_contract=f"Resolver {dl_fn}",
|
||||
business_meaning="Kurzlisten für Coaching-Prompts",
|
||||
unit="text",
|
||||
time_window="aktuell",
|
||||
output_type=OutputType.TEXT_SUMMARY,
|
||||
placeholder_type=PlaceholderType.INTERPRETED,
|
||||
format_hint="Freitext / Aufzählung",
|
||||
example_output=ex,
|
||||
minimum_data_requirements="Ziele / Fokusdaten",
|
||||
quality_filter_policy=None,
|
||||
confidence_logic=dl_fn,
|
||||
missing_value_policy=MVP("insufficient_data", "nicht verfügbar"),
|
||||
known_limitations=None,
|
||||
layer_1_decision="goals + focus aggregation",
|
||||
layer_2a_decision=dl_fn,
|
||||
layer_2b_reuse_possible=False,
|
||||
architecture_alignment="Phase 0b",
|
||||
issue_53_alignment="Layer 2a",
|
||||
evidence={},
|
||||
)
|
||||
tag_standard_evidence(m)
|
||||
register_placeholder(m)
|
||||
|
||||
|
||||
register_phase_0b_ziele_fokus()
|
||||
139
backend/placeholder_registrations/profil_zeitraum.py
Normal file
139
backend/placeholder_registrations/profil_zeitraum.py
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
"""
|
||||
Registry: Profil-Stammdaten und statische Zeitraum-Labels für Prompts.
|
||||
"""
|
||||
|
||||
from placeholder_registry import (
|
||||
PlaceholderMetadata,
|
||||
MissingValuePolicy,
|
||||
OutputType,
|
||||
PlaceholderType,
|
||||
register_placeholder,
|
||||
)
|
||||
from ._evidence import tag_standard_evidence
|
||||
|
||||
MVP = lambda reason, disp: MissingValuePolicy(
|
||||
available=False, value_raw=None, missing_reason=reason, legacy_display=disp
|
||||
)
|
||||
|
||||
|
||||
def register_profil_zeitraum():
|
||||
cat_profil = "Profil"
|
||||
for key, desc, res_fn, unit, ptype, out, hint, ex, sem in [
|
||||
(
|
||||
"name",
|
||||
"Anzeigename aus profiles.name",
|
||||
"get_profile_name",
|
||||
"text",
|
||||
PlaceholderType.ATOMIC,
|
||||
OutputType.STRING,
|
||||
"Kurzname",
|
||||
"Max",
|
||||
"profiles.name, Fallback „Nutzer“.",
|
||||
),
|
||||
(
|
||||
"age",
|
||||
"Alter in Jahren aus profiles.dob",
|
||||
"get_profile_age_display",
|
||||
"Jahre",
|
||||
PlaceholderType.ATOMIC,
|
||||
OutputType.STRING,
|
||||
"Ganzzahl oder unbekannt",
|
||||
"42",
|
||||
"Berechnung aus Geburtsdatum; PostgreSQL date oder ISO-String.",
|
||||
),
|
||||
(
|
||||
"height",
|
||||
"Körpergröße (cm) aus profiles.height",
|
||||
"get_profile_height_display",
|
||||
"cm",
|
||||
PlaceholderType.ATOMIC,
|
||||
OutputType.STRING,
|
||||
"Zahl oder unbekannt",
|
||||
"180",
|
||||
"profiles.height.",
|
||||
),
|
||||
(
|
||||
"geschlecht",
|
||||
"Geschlecht (männlich/weiblich) aus profiles.sex",
|
||||
"get_profile_geschlecht_display",
|
||||
"Kategorie",
|
||||
PlaceholderType.ATOMIC,
|
||||
OutputType.STRING,
|
||||
"m/w-Mapping",
|
||||
"männlich",
|
||||
"sex == 'm' → männlich, sonst weiblich.",
|
||||
),
|
||||
]:
|
||||
m = PlaceholderMetadata(
|
||||
key=key,
|
||||
category=cat_profil,
|
||||
description=desc,
|
||||
resolver_module="backend/placeholder_resolver.py",
|
||||
resolver_function=res_fn,
|
||||
data_layer_module=None,
|
||||
data_layer_function=None,
|
||||
source_tables=["profiles"],
|
||||
semantic_contract=sem,
|
||||
business_meaning="Profil-Kontext für KI-Prompts",
|
||||
unit=unit,
|
||||
time_window="latest profile row",
|
||||
output_type=out,
|
||||
placeholder_type=ptype,
|
||||
format_hint=hint,
|
||||
example_output=ex,
|
||||
minimum_data_requirements="Profilzeile",
|
||||
quality_filter_policy=None,
|
||||
confidence_logic="Row vorhanden",
|
||||
missing_value_policy=MVP("no_data", "unbekannt" if key != "name" else "Nutzer"),
|
||||
known_limitations="Keine diversen Geschlechtsoptionen im Platzhalter",
|
||||
layer_1_decision="profiles",
|
||||
layer_2a_decision=res_fn,
|
||||
layer_2b_reuse_possible=False,
|
||||
architecture_alignment="Phase 0b",
|
||||
issue_53_alignment="Resolver",
|
||||
evidence={},
|
||||
)
|
||||
tag_standard_evidence(m)
|
||||
register_placeholder(m)
|
||||
|
||||
cat_zeit = "Zeitraum"
|
||||
for key, desc, res_fn, sem, ex_out in [
|
||||
("datum_heute", "Heutiges Datum (lokal)", "get_datum_heute", "datetime.now, Format dd.mm.yyyy", "11.04.2026"),
|
||||
("zeitraum_7d", "Label „letzte 7 Tage“", "get_zeitraum_label_7d", "Statisches UI/Prompt-Label", "letzte 7 Tage"),
|
||||
("zeitraum_30d", "Label „letzte 30 Tage“", "get_zeitraum_label_30d", "Statisches UI/Prompt-Label", "letzte 30 Tage"),
|
||||
("zeitraum_90d", "Label „letzte 90 Tage“", "get_zeitraum_label_90d", "Statisches UI/Prompt-Label", "letzte 90 Tage"),
|
||||
]:
|
||||
m = PlaceholderMetadata(
|
||||
key=key,
|
||||
category=cat_zeit,
|
||||
description=desc,
|
||||
resolver_module="backend/placeholder_resolver.py",
|
||||
resolver_function=res_fn,
|
||||
data_layer_module=None,
|
||||
data_layer_function=None,
|
||||
source_tables=[],
|
||||
semantic_contract=sem,
|
||||
business_meaning="Zeitlicher Bezug im Prompt ohne Datenabfrage",
|
||||
unit="label",
|
||||
time_window="n/a",
|
||||
output_type=OutputType.STRING,
|
||||
placeholder_type=PlaceholderType.META,
|
||||
format_hint="Kurzdeutsch",
|
||||
example_output=ex_out,
|
||||
minimum_data_requirements=None,
|
||||
quality_filter_policy=None,
|
||||
confidence_logic="Immer verfügbar",
|
||||
missing_value_policy=None,
|
||||
known_limitations="Kein kalender-basierter Datenfilter allein durch Platzhalter",
|
||||
layer_1_decision="n/a",
|
||||
layer_2a_decision=res_fn,
|
||||
layer_2b_reuse_possible=False,
|
||||
architecture_alignment="Phase 0b",
|
||||
issue_53_alignment="Resolver",
|
||||
evidence={},
|
||||
)
|
||||
tag_standard_evidence(m)
|
||||
register_placeholder(m)
|
||||
|
||||
|
||||
register_profil_zeitraum()
|
||||
236
backend/placeholder_registrations/schlaf_erholung.py
Normal file
236
backend/placeholder_registrations/schlaf_erholung.py
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
"""
|
||||
Registry: Schlaf, Ruhetage, Recovery-Score, Schlaf-Metriken, Schlaf-Erholungs-Korrelation.
|
||||
"""
|
||||
|
||||
from placeholder_registry import (
|
||||
PlaceholderMetadata,
|
||||
MissingValuePolicy,
|
||||
EvidenceType,
|
||||
OutputType,
|
||||
PlaceholderType,
|
||||
register_placeholder,
|
||||
)
|
||||
|
||||
CAT = "Schlaf & Erholung"
|
||||
MVP = lambda reason, disp: MissingValuePolicy(
|
||||
available=False, value_raw=None, missing_reason=reason, legacy_display=disp
|
||||
)
|
||||
|
||||
|
||||
def _tag(m: PlaceholderMetadata):
|
||||
for f in (
|
||||
"key", "category", "description", "resolver_module", "resolver_function",
|
||||
"data_layer_module", "data_layer_function", "source_tables", "semantic_contract",
|
||||
"unit", "time_window", "output_type", "placeholder_type", "format_hint",
|
||||
"example_output", "minimum_data_requirements", "confidence_logic",
|
||||
"missing_value_policy", "layer_1_decision", "layer_2a_decision",
|
||||
"layer_2b_reuse_possible", "architecture_alignment", "issue_53_alignment",
|
||||
):
|
||||
m.set_evidence(f, EvidenceType.CODE_DERIVED)
|
||||
m.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
|
||||
m.set_evidence("known_limitations", EvidenceType.MIXED)
|
||||
|
||||
|
||||
def register_schlaf_erholung():
|
||||
# ── formatierte Schlaf-/Ruhetage-Snapshots ───────────────────────────────
|
||||
m = PlaceholderMetadata(
|
||||
key="sleep_avg_duration",
|
||||
category=CAT,
|
||||
description="Durchschnittliche Schlafdauer (Stunden), formatiert",
|
||||
resolver_module="backend/placeholder_resolver.py",
|
||||
resolver_function="get_sleep_avg_duration",
|
||||
data_layer_module="backend/data_layer/recovery_metrics.py",
|
||||
data_layer_function="get_sleep_duration_data",
|
||||
source_tables=["sleep_log"],
|
||||
semantic_contract="Mittel aus Schlafphasen im Fenster (siehe get_sleep_duration_data).",
|
||||
business_meaning="KI-Kontext Schlafdauer",
|
||||
unit="h (Anzeige mit Einheit)",
|
||||
time_window="7d default im Resolver",
|
||||
output_type=OutputType.STRING,
|
||||
placeholder_type=PlaceholderType.INTERPRETED,
|
||||
format_hint="z. B. 7.2h",
|
||||
example_output="7.2h",
|
||||
minimum_data_requirements="sleep_log im Fenster",
|
||||
quality_filter_policy=None,
|
||||
confidence_logic="data['confidence'] im Layer1",
|
||||
missing_value_policy=MVP("no_data", "nicht verfügbar"),
|
||||
known_limitations="Abhängig von Import/Qualität der Phasen",
|
||||
layer_1_decision="recovery_metrics.get_sleep_duration_data",
|
||||
layer_2a_decision="get_sleep_avg_duration",
|
||||
layer_2b_reuse_possible=True,
|
||||
architecture_alignment="Phase 0c",
|
||||
issue_53_alignment="Layer 1",
|
||||
evidence={},
|
||||
)
|
||||
_tag(m)
|
||||
register_placeholder(m)
|
||||
|
||||
m = PlaceholderMetadata(
|
||||
key="sleep_avg_quality",
|
||||
category=CAT,
|
||||
description="Schlafqualität (Deep+REM %), formatiert",
|
||||
resolver_module="backend/placeholder_resolver.py",
|
||||
resolver_function="get_sleep_avg_quality",
|
||||
data_layer_module="backend/data_layer/recovery_metrics.py",
|
||||
data_layer_function="get_sleep_quality_data",
|
||||
source_tables=["sleep_log"],
|
||||
semantic_contract="Anteil Deep+REM aus Segmenten (siehe get_sleep_quality_data).",
|
||||
business_meaning="KI-Kontext Schlafqualität",
|
||||
unit="%",
|
||||
time_window="7d default",
|
||||
output_type=OutputType.STRING,
|
||||
placeholder_type=PlaceholderType.INTERPRETED,
|
||||
format_hint="Prozent oder nicht verfügbar",
|
||||
example_output="24%",
|
||||
minimum_data_requirements="sleep_log mit Phasen",
|
||||
quality_filter_policy=None,
|
||||
confidence_logic="Layer-1-Confidence",
|
||||
missing_value_policy=MVP("no_data", "nicht verfügbar"),
|
||||
known_limitations="Segment-Schreibweise case-sensitiv normalisiert",
|
||||
layer_1_decision="recovery_metrics.get_sleep_quality_data",
|
||||
layer_2a_decision="get_sleep_avg_quality",
|
||||
layer_2b_reuse_possible=True,
|
||||
architecture_alignment="Phase 0c",
|
||||
issue_53_alignment="Layer 1",
|
||||
evidence={},
|
||||
)
|
||||
_tag(m)
|
||||
register_placeholder(m)
|
||||
|
||||
m = PlaceholderMetadata(
|
||||
key="rest_days_count",
|
||||
category=CAT,
|
||||
description="Anzahl dokumentierter Ruhetage (30d default)",
|
||||
resolver_module="backend/placeholder_resolver.py",
|
||||
resolver_function="get_rest_days_count",
|
||||
data_layer_module="backend/data_layer/recovery_metrics.py",
|
||||
data_layer_function="get_rest_days_data",
|
||||
source_tables=["rest_days"],
|
||||
semantic_contract="Count rest_days im Zeitraum",
|
||||
business_meaning="Aktive/passive Erholungstags-Übersicht",
|
||||
unit="count",
|
||||
time_window="30d default",
|
||||
output_type=OutputType.STRING,
|
||||
placeholder_type=PlaceholderType.RAW_DATA,
|
||||
format_hint="z. B. 2 Ruhetage",
|
||||
example_output="2 Ruhetage",
|
||||
minimum_data_requirements="rest_days",
|
||||
quality_filter_policy=None,
|
||||
confidence_logic="Immer Zählung, 0 möglich",
|
||||
missing_value_policy=MVP("no_data", "0 Ruhetage"),
|
||||
known_limitations="Nur explizit erfasste Ruhetage",
|
||||
layer_1_decision="recovery_metrics.get_rest_days_data",
|
||||
layer_2a_decision="get_rest_days_count",
|
||||
layer_2b_reuse_possible=True,
|
||||
architecture_alignment="Phase 0c",
|
||||
issue_53_alignment="Layer 1",
|
||||
evidence={},
|
||||
)
|
||||
_tag(m)
|
||||
register_placeholder(m)
|
||||
|
||||
m = PlaceholderMetadata(
|
||||
key="recovery_score",
|
||||
category=CAT,
|
||||
description="Recovery-Score 0–100 (v2, komposit)",
|
||||
resolver_module="backend/placeholder_resolver.py",
|
||||
resolver_function="_safe_int",
|
||||
data_layer_module="backend/data_layer/recovery_metrics.py",
|
||||
data_layer_function="calculate_recovery_score_v2",
|
||||
source_tables=["sleep_log", "vitals_baseline", "activity_log"],
|
||||
semantic_contract="Gewichteter Score aus Schlaf, Vitaltrends, optional Load (siehe Implementierung).",
|
||||
business_meaning="Gesamt-Recovery-KPI für Prompts",
|
||||
unit="0–100",
|
||||
time_window="composite",
|
||||
output_type=OutputType.NUMERIC,
|
||||
placeholder_type=PlaceholderType.SCORE,
|
||||
format_hint="Ganzzahl-String",
|
||||
example_output="72",
|
||||
minimum_data_requirements="Teilkomponenten je nach Gewichtung",
|
||||
quality_filter_policy=None,
|
||||
confidence_logic="Wie calculate_recovery_score_v2",
|
||||
missing_value_policy=MVP("insufficient_data", "nicht verfügbar"),
|
||||
known_limitations="Abhängig von Datenabdeckung HF/HRV/Schlaf",
|
||||
layer_1_decision="recovery_metrics.calculate_recovery_score_v2",
|
||||
layer_2a_decision="_safe_int('recovery_score_v2')",
|
||||
layer_2b_reuse_possible=True,
|
||||
architecture_alignment="Phase 0c",
|
||||
issue_53_alignment="Layer 1",
|
||||
evidence={},
|
||||
)
|
||||
_tag(m)
|
||||
register_placeholder(m)
|
||||
|
||||
for key, dl_fn, desc, unit, tbls, res_fn in [
|
||||
("sleep_avg_duration_7d", "calculate_sleep_avg_duration_7d", "Durchschnittliche Schlafdauer 7d (h)", "h", ["sleep_log"], "_safe_float"),
|
||||
("sleep_debt_hours", "calculate_sleep_debt_hours", "Kumulative Schlafschuld (h)", "h", ["sleep_log"], "_safe_float"),
|
||||
("sleep_regularity_proxy", "calculate_sleep_regularity_proxy", "Schlaf-Regularität (Proxy)", "min", ["sleep_log"], "_safe_float"),
|
||||
("recent_load_balance_3d", "calculate_recent_load_balance_3d", "Load-Balance 3d (Score)", "score", ["activity_log"], "_safe_int"),
|
||||
("sleep_quality_7d", "calculate_sleep_quality_7d", "Schlafqualität 7d (0–100)", "0-100", ["sleep_log"], "_safe_int"),
|
||||
]:
|
||||
m = PlaceholderMetadata(
|
||||
key=key,
|
||||
category=CAT,
|
||||
description=desc,
|
||||
resolver_module="backend/placeholder_resolver.py",
|
||||
resolver_function=res_fn,
|
||||
data_layer_module="backend/data_layer/recovery_metrics.py",
|
||||
data_layer_function=dl_fn,
|
||||
source_tables=tbls,
|
||||
semantic_contract=f"Berechnung {dl_fn} in recovery_metrics.",
|
||||
business_meaning="Erholungs-Detailmetrik",
|
||||
unit=unit,
|
||||
time_window="siehe Funktion",
|
||||
output_type=OutputType.NUMERIC,
|
||||
placeholder_type=PlaceholderType.INTERPRETED,
|
||||
format_hint="numerischer String",
|
||||
example_output="1.0",
|
||||
minimum_data_requirements="wie Funktion",
|
||||
quality_filter_policy=None,
|
||||
confidence_logic="Funktionsintern",
|
||||
missing_value_policy=MVP("insufficient_data", "nicht verfügbar"),
|
||||
known_limitations=None,
|
||||
layer_1_decision=f"recovery_metrics.{dl_fn}",
|
||||
layer_2a_decision="Resolver _safe_float/_safe_int",
|
||||
layer_2b_reuse_possible=True,
|
||||
architecture_alignment="Phase 0c",
|
||||
issue_53_alignment="Layer 1",
|
||||
evidence={},
|
||||
)
|
||||
_tag(m)
|
||||
register_placeholder(m)
|
||||
|
||||
m = PlaceholderMetadata(
|
||||
key="correlation_sleep_recovery",
|
||||
category=CAT,
|
||||
description="JSON: Korrelation Schlaf ↔ Recovery-Indikatoren",
|
||||
resolver_module="backend/placeholder_resolver.py",
|
||||
resolver_function="_safe_json",
|
||||
data_layer_module="backend/data_layer/correlations.py",
|
||||
data_layer_function="calculate_correlation_sleep_recovery",
|
||||
source_tables=["sleep_log", "vitals_baseline", "activity_log"],
|
||||
semantic_contract="Strukturierte Korrelationsauswertung (siehe correlations).",
|
||||
business_meaning="KI: Zusammenhänge Schlaf und Erholung",
|
||||
unit="JSON",
|
||||
time_window="funktionsabhängig",
|
||||
output_type=OutputType.JSON,
|
||||
placeholder_type=PlaceholderType.RAW_DATA,
|
||||
format_hint="JSON-String",
|
||||
example_output="{}",
|
||||
minimum_data_requirements="Ausreichend gekoppelte Datenpunkte",
|
||||
quality_filter_policy=None,
|
||||
confidence_logic="Wie correlation_metrics",
|
||||
missing_value_policy=MVP("insufficient_data", "{}"),
|
||||
known_limitations="Bei wenig Daten leer oder schwach",
|
||||
layer_1_decision="correlations.calculate_correlation_sleep_recovery",
|
||||
layer_2a_decision="_safe_json",
|
||||
layer_2b_reuse_possible=True,
|
||||
architecture_alignment="Phase 0c",
|
||||
issue_53_alignment="Layer 1",
|
||||
evidence={},
|
||||
)
|
||||
_tag(m)
|
||||
register_placeholder(m)
|
||||
|
||||
|
||||
register_schlaf_erholung()
|
||||
180
backend/placeholder_registrations/vitalwerte.py
Normal file
180
backend/placeholder_registrations/vitalwerte.py
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
"""
|
||||
Registry: Baseline-Vitals (Ruhepuls, HRV, VO2 Max) und Abweichung vs. persönlicher Baseline.
|
||||
"""
|
||||
|
||||
from placeholder_registry import (
|
||||
PlaceholderMetadata,
|
||||
MissingValuePolicy,
|
||||
EvidenceType,
|
||||
OutputType,
|
||||
PlaceholderType,
|
||||
register_placeholder,
|
||||
)
|
||||
|
||||
CAT = "Vitalwerte"
|
||||
MVP = lambda reason, disp: MissingValuePolicy(
|
||||
available=False, value_raw=None, missing_reason=reason, legacy_display=disp
|
||||
)
|
||||
|
||||
|
||||
def _tag(m: PlaceholderMetadata):
|
||||
for f in (
|
||||
"key", "category", "description", "resolver_module", "resolver_function",
|
||||
"data_layer_module", "data_layer_function", "source_tables", "semantic_contract",
|
||||
"unit", "time_window", "output_type", "placeholder_type", "format_hint",
|
||||
"example_output", "minimum_data_requirements", "confidence_logic",
|
||||
"missing_value_policy", "layer_1_decision", "layer_2a_decision",
|
||||
"layer_2b_reuse_possible", "architecture_alignment", "issue_53_alignment",
|
||||
):
|
||||
m.set_evidence(f, EvidenceType.CODE_DERIVED)
|
||||
m.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
|
||||
m.set_evidence("known_limitations", EvidenceType.MIXED)
|
||||
|
||||
|
||||
def register_vitalwerte():
|
||||
m = PlaceholderMetadata(
|
||||
key="vitals_avg_hr",
|
||||
category=CAT,
|
||||
description="Durchschnittlicher Ruhepuls (7d), formatiert",
|
||||
resolver_module="backend/placeholder_resolver.py",
|
||||
resolver_function="get_vitals_avg_hr",
|
||||
data_layer_module="backend/data_layer/health_metrics.py",
|
||||
data_layer_function="get_resting_heart_rate_data",
|
||||
source_tables=["vitals_baseline"],
|
||||
semantic_contract="Mittel RHR aus vitals_baseline im Fenster (siehe health_metrics).",
|
||||
business_meaning="KI-Kontext kardiovaskuläre Ruhelage",
|
||||
unit="bpm (Anzeige mit Einheit)",
|
||||
time_window="7d default im Resolver",
|
||||
output_type=OutputType.STRING,
|
||||
placeholder_type=PlaceholderType.INTERPRETED,
|
||||
format_hint="z. B. 58 bpm",
|
||||
example_output="58 bpm",
|
||||
minimum_data_requirements="vitals_baseline im Fenster",
|
||||
quality_filter_policy=None,
|
||||
confidence_logic="data['confidence'] im Layer1",
|
||||
missing_value_policy=MVP("no_data", "nicht verfügbar"),
|
||||
known_limitations="Nur erfasste Morgen-Baseline-Messungen",
|
||||
layer_1_decision="health_metrics.get_resting_heart_rate_data",
|
||||
layer_2a_decision="get_vitals_avg_hr",
|
||||
layer_2b_reuse_possible=True,
|
||||
architecture_alignment="Phase 0c",
|
||||
issue_53_alignment="Layer 1",
|
||||
evidence={},
|
||||
)
|
||||
_tag(m)
|
||||
register_placeholder(m)
|
||||
|
||||
m = PlaceholderMetadata(
|
||||
key="vitals_avg_hrv",
|
||||
category=CAT,
|
||||
description="Durchschnittliche HRV (7d), formatiert",
|
||||
resolver_module="backend/placeholder_resolver.py",
|
||||
resolver_function="get_vitals_avg_hrv",
|
||||
data_layer_module="backend/data_layer/health_metrics.py",
|
||||
data_layer_function="get_heart_rate_variability_data",
|
||||
source_tables=["vitals_baseline"],
|
||||
semantic_contract="Mittel HRV aus vitals_baseline im Fenster.",
|
||||
business_meaning="KI-Kontext autonome Regulation / Erholung",
|
||||
unit="ms (Anzeige mit Einheit)",
|
||||
time_window="7d default",
|
||||
output_type=OutputType.STRING,
|
||||
placeholder_type=PlaceholderType.INTERPRETED,
|
||||
format_hint="z. B. 45 ms",
|
||||
example_output="45 ms",
|
||||
minimum_data_requirements="vitals_baseline mit HRV",
|
||||
quality_filter_policy=None,
|
||||
confidence_logic="data['confidence'] im Layer1",
|
||||
missing_value_policy=MVP("no_data", "nicht verfügbar"),
|
||||
known_limitations="Geräte-/Messprotokoll kann streuen",
|
||||
layer_1_decision="health_metrics.get_heart_rate_variability_data",
|
||||
layer_2a_decision="get_vitals_avg_hrv",
|
||||
layer_2b_reuse_possible=True,
|
||||
architecture_alignment="Phase 0c",
|
||||
issue_53_alignment="Layer 1",
|
||||
evidence={},
|
||||
)
|
||||
_tag(m)
|
||||
register_placeholder(m)
|
||||
|
||||
m = PlaceholderMetadata(
|
||||
key="vitals_vo2_max",
|
||||
category=CAT,
|
||||
description="Aktueller VO2 Max (letzte Messung), formatiert",
|
||||
resolver_module="backend/placeholder_resolver.py",
|
||||
resolver_function="get_vitals_vo2_max",
|
||||
data_layer_module="backend/data_layer/health_metrics.py",
|
||||
data_layer_function="get_vo2_max_data",
|
||||
source_tables=["vitals_baseline"],
|
||||
semantic_contract="Jüngster vo2_max aus vitals_baseline.",
|
||||
business_meaning="Ausdauer-/Fitness-Kontext",
|
||||
unit="ml/kg/min",
|
||||
time_window="latest",
|
||||
output_type=OutputType.STRING,
|
||||
placeholder_type=PlaceholderType.INTERPRETED,
|
||||
format_hint="eine Dezimalstelle + Einheit",
|
||||
example_output="42.0 ml/kg/min",
|
||||
minimum_data_requirements="mindestens eine VO2-Messung",
|
||||
quality_filter_policy=None,
|
||||
confidence_logic="data['confidence'] im Layer1",
|
||||
missing_value_policy=MVP("no_data", "nicht verfügbar"),
|
||||
known_limitations="Schätzung vs. Labortest je nach Quelle",
|
||||
layer_1_decision="health_metrics.get_vo2_max_data",
|
||||
layer_2a_decision="get_vitals_vo2_max",
|
||||
layer_2b_reuse_possible=True,
|
||||
architecture_alignment="Phase 0c",
|
||||
issue_53_alignment="Layer 1",
|
||||
evidence={},
|
||||
)
|
||||
_tag(m)
|
||||
register_placeholder(m)
|
||||
|
||||
for key, dl_fn, desc, unit, res_fn in [
|
||||
(
|
||||
"hrv_vs_baseline_pct",
|
||||
"calculate_hrv_vs_baseline_pct",
|
||||
"HRV vs. persönlicher Baseline (%)",
|
||||
"%",
|
||||
"_safe_float",
|
||||
),
|
||||
(
|
||||
"rhr_vs_baseline_pct",
|
||||
"calculate_rhr_vs_baseline_pct",
|
||||
"Ruhepuls vs. persönlicher Baseline (%)",
|
||||
"%",
|
||||
"_safe_float",
|
||||
),
|
||||
]:
|
||||
m = PlaceholderMetadata(
|
||||
key=key,
|
||||
category=CAT,
|
||||
description=desc,
|
||||
resolver_module="backend/placeholder_resolver.py",
|
||||
resolver_function=res_fn,
|
||||
data_layer_module="backend/data_layer/recovery_metrics.py",
|
||||
data_layer_function=dl_fn,
|
||||
source_tables=["vitals_baseline"],
|
||||
semantic_contract=f"Vergleich aktueller Wert zu Baseline (siehe {dl_fn}).",
|
||||
business_meaning="Erholungs- und Belastungsindikator relativ zur Norm des Nutzers",
|
||||
unit=unit,
|
||||
time_window="funktionsintern",
|
||||
output_type=OutputType.NUMERIC,
|
||||
placeholder_type=PlaceholderType.INTERPRETED,
|
||||
format_hint="numerischer Prozent-String",
|
||||
example_output="5.2",
|
||||
minimum_data_requirements="Ausreichend Baseline-Historie",
|
||||
quality_filter_policy=None,
|
||||
confidence_logic="Funktionsintern",
|
||||
missing_value_policy=MVP("insufficient_data", "nicht verfügbar"),
|
||||
known_limitations="Baseline braucht ausreichend Vorlauf",
|
||||
layer_1_decision=f"recovery_metrics.{dl_fn}",
|
||||
layer_2a_decision=f"Resolver {res_fn}",
|
||||
layer_2b_reuse_possible=True,
|
||||
architecture_alignment="Phase 0c",
|
||||
issue_53_alignment="Layer 1",
|
||||
evidence={},
|
||||
)
|
||||
_tag(m)
|
||||
register_placeholder(m)
|
||||
|
||||
|
||||
register_vitalwerte()
|
||||
|
|
@ -9,7 +9,7 @@ This module now focuses on FORMATTING for AI consumption.
|
|||
"""
|
||||
import re
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Dict, List, Optional, Callable
|
||||
from typing import Dict, List, Optional, Callable, Tuple
|
||||
from db import get_db, get_cursor, r2d
|
||||
|
||||
# Phase 0c: Import data layer
|
||||
|
|
@ -277,6 +277,43 @@ def calculate_age(dob) -> str:
|
|||
return "unbekannt"
|
||||
|
||||
|
||||
def get_profile_name(profile_id: str) -> str:
|
||||
"""Profil-Platzhalter: Anzeigename (profiles.name)."""
|
||||
return get_profile_data(profile_id).get('name', 'Nutzer')
|
||||
|
||||
|
||||
def get_profile_age_display(profile_id: str) -> str:
|
||||
"""Profil-Platzhalter: Alter aus Geburtsdatum."""
|
||||
return calculate_age(get_profile_data(profile_id).get('dob'))
|
||||
|
||||
|
||||
def get_profile_height_display(profile_id: str) -> str:
|
||||
"""Profil-Platzhalter: Körpergröße (cm) als String."""
|
||||
return str(get_profile_data(profile_id).get('height', 'unbekannt'))
|
||||
|
||||
|
||||
def get_profile_geschlecht_display(profile_id: str) -> str:
|
||||
"""Profil-Platzhalter: Geschlecht aus profiles.sex (m/w)."""
|
||||
return 'männlich' if get_profile_data(profile_id).get('sex') == 'm' else 'weiblich'
|
||||
|
||||
|
||||
def get_datum_heute(_profile_id: str) -> str:
|
||||
"""Zeitraum-Platzhalter: heutiges Datum (dd.mm.yyyy)."""
|
||||
return datetime.now().strftime('%d.%m.%Y')
|
||||
|
||||
|
||||
def get_zeitraum_label_7d(_profile_id: str) -> str:
|
||||
return 'letzte 7 Tage'
|
||||
|
||||
|
||||
def get_zeitraum_label_30d(_profile_id: str) -> str:
|
||||
return 'letzte 30 Tage'
|
||||
|
||||
|
||||
def get_zeitraum_label_90d(_profile_id: str) -> str:
|
||||
return 'letzte 90 Tage'
|
||||
|
||||
|
||||
def get_activity_detail(profile_id: str, days: int = 14) -> str:
|
||||
"""
|
||||
Get detailed activity log for analysis.
|
||||
|
|
@ -1136,10 +1173,10 @@ def _format_goals_on_track(profile_id: str, n: int = 3) -> str:
|
|||
|
||||
PLACEHOLDER_MAP: Dict[str, Callable[[str], str]] = {
|
||||
# Profil
|
||||
'{{name}}': lambda pid: get_profile_data(pid).get('name', 'Nutzer'),
|
||||
'{{age}}': lambda pid: calculate_age(get_profile_data(pid).get('dob')),
|
||||
'{{height}}': lambda pid: str(get_profile_data(pid).get('height', 'unbekannt')),
|
||||
'{{geschlecht}}': lambda pid: 'männlich' if get_profile_data(pid).get('sex') == 'm' else 'weiblich',
|
||||
'{{name}}': get_profile_name,
|
||||
'{{age}}': get_profile_age_display,
|
||||
'{{height}}': get_profile_height_display,
|
||||
'{{geschlecht}}': get_profile_geschlecht_display,
|
||||
|
||||
# Körper (21 Registry-Keys: body_metrics + body_extras — alles hier gebündelt)
|
||||
'{{weight_aktuell}}': get_latest_weight,
|
||||
|
|
@ -1203,29 +1240,37 @@ PLACEHOLDER_MAP: Dict[str, Callable[[str], str]] = {
|
|||
'{{training_inter_session_gap_md}}': get_training_inter_session_gap_md,
|
||||
'{{training_sessions_recent_json}}': lambda pid: _safe_json('training_sessions_recent_json', pid),
|
||||
|
||||
# Schlaf & Erholung
|
||||
# Schlaf & Erholung (10 Registry-Keys; recovery_score hier, nicht unter Meta Scores)
|
||||
'{{sleep_avg_duration}}': lambda pid: get_sleep_avg_duration(pid, 7),
|
||||
'{{sleep_avg_quality}}': lambda pid: get_sleep_avg_quality(pid, 7),
|
||||
'{{rest_days_count}}': lambda pid: get_rest_days_count(pid, 30),
|
||||
'{{recovery_score}}': lambda pid: _safe_int('recovery_score_v2', pid),
|
||||
'{{sleep_avg_duration_7d}}': lambda pid: _safe_float('sleep_avg_duration_7d', pid),
|
||||
'{{sleep_debt_hours}}': lambda pid: _safe_float('sleep_debt_hours', pid),
|
||||
'{{sleep_regularity_proxy}}': lambda pid: _safe_float('sleep_regularity_proxy', pid),
|
||||
'{{recent_load_balance_3d}}': lambda pid: _safe_int('recent_load_balance_3d', pid),
|
||||
'{{sleep_quality_7d}}': lambda pid: _safe_int('sleep_quality_7d', pid),
|
||||
'{{correlation_sleep_recovery}}': lambda pid: _safe_json('correlation_sleep_recovery', pid),
|
||||
|
||||
# Vitalwerte
|
||||
# Vitalwerte (5 Registry-Keys: Mittelwerte + vs. Baseline)
|
||||
'{{vitals_avg_hr}}': lambda pid: get_vitals_avg_hr(pid, 7),
|
||||
'{{vitals_avg_hrv}}': lambda pid: get_vitals_avg_hrv(pid, 7),
|
||||
'{{vitals_vo2_max}}': get_vitals_vo2_max,
|
||||
'{{hrv_vs_baseline_pct}}': lambda pid: _safe_float('hrv_vs_baseline_pct', pid),
|
||||
'{{rhr_vs_baseline_pct}}': lambda pid: _safe_float('rhr_vs_baseline_pct', pid),
|
||||
|
||||
# Zeitraum
|
||||
'{{datum_heute}}': lambda pid: datetime.now().strftime('%d.%m.%Y'),
|
||||
'{{zeitraum_7d}}': lambda pid: 'letzte 7 Tage',
|
||||
'{{zeitraum_30d}}': lambda pid: 'letzte 30 Tage',
|
||||
'{{zeitraum_90d}}': lambda pid: 'letzte 90 Tage',
|
||||
'{{datum_heute}}': get_datum_heute,
|
||||
'{{zeitraum_7d}}': get_zeitraum_label_7d,
|
||||
'{{zeitraum_30d}}': get_zeitraum_label_30d,
|
||||
'{{zeitraum_90d}}': get_zeitraum_label_90d,
|
||||
|
||||
# ========================================================================
|
||||
# PHASE 0b: Goal-Aware Placeholders (Dynamic Focus Areas v2.0)
|
||||
# ========================================================================
|
||||
|
||||
# --- Meta Scores (Ebene 1: Aggregierte Scores; body/nutrition/activity scores → jeweilige Kategorie) ---
|
||||
# --- Meta Scores (Ebene 1; recovery_score → Schlaf & Erholung) ---
|
||||
'{{goal_progress_score}}': lambda pid: _safe_int('goal_progress_score', pid),
|
||||
'{{recovery_score}}': lambda pid: _safe_int('recovery_score_v2', pid),
|
||||
'{{data_quality_score}}': lambda pid: _safe_int('data_quality_score', pid),
|
||||
|
||||
# --- Top-Weighted Goals/Focus Areas (Ebene 2: statt Primary) ---
|
||||
|
|
@ -1251,21 +1296,11 @@ PLACEHOLDER_MAP: Dict[str, Callable[[str], str]] = {
|
|||
'{{focus_cat_lebensstil_progress}}': lambda pid: _safe_int('focus_cat_lebensstil_progress', pid),
|
||||
'{{focus_cat_lebensstil_weight}}': lambda pid: _safe_float('focus_cat_lebensstil_weight', pid),
|
||||
|
||||
# --- Recovery Metrics (Recovery Score v2) ---
|
||||
'{{hrv_vs_baseline_pct}}': lambda pid: _safe_float('hrv_vs_baseline_pct', pid),
|
||||
'{{rhr_vs_baseline_pct}}': lambda pid: _safe_float('rhr_vs_baseline_pct', pid),
|
||||
'{{sleep_avg_duration_7d}}': lambda pid: _safe_float('sleep_avg_duration_7d', pid),
|
||||
'{{sleep_debt_hours}}': lambda pid: _safe_float('sleep_debt_hours', pid),
|
||||
'{{sleep_regularity_proxy}}': lambda pid: _safe_float('sleep_regularity_proxy', pid),
|
||||
'{{recent_load_balance_3d}}': lambda pid: _safe_int('recent_load_balance_3d', pid),
|
||||
'{{sleep_quality_7d}}': lambda pid: _safe_int('sleep_quality_7d', pid),
|
||||
|
||||
# --- Correlation Metrics (C1-C7) ---
|
||||
'{{correlation_energy_weight_lag}}': lambda pid: _safe_json('correlation_energy_weight_lag', pid),
|
||||
'{{correlation_protein_lbm}}': lambda pid: _safe_json('correlation_protein_lbm', pid),
|
||||
'{{correlation_load_hrv}}': lambda pid: _safe_json('correlation_load_hrv', pid),
|
||||
'{{correlation_load_rhr}}': lambda pid: _safe_json('correlation_load_rhr', pid),
|
||||
'{{correlation_sleep_recovery}}': lambda pid: _safe_json('correlation_sleep_recovery', pid),
|
||||
'{{plateau_detected}}': lambda pid: _safe_json('plateau_detected', pid),
|
||||
'{{top_drivers}}': lambda pid: _safe_json('top_drivers', pid),
|
||||
|
||||
|
|
@ -1378,9 +1413,42 @@ def get_available_placeholders(categories: Optional[List[str]] = None) -> Dict[s
|
|||
'{{rest_day_compliance}}', '{{vo2max_trend_28d}}', '{{activity_score}}',
|
||||
'{{training_frequency_by_type_md}}', '{{training_inter_session_gap_md}}', '{{training_sessions_recent_json}}',
|
||||
],
|
||||
'schlaf': [
|
||||
'{{sleep_avg_duration}}', '{{sleep_avg_quality}}', '{{rest_days_count}}',
|
||||
'{{recovery_score}}',
|
||||
'{{sleep_avg_duration_7d}}', '{{sleep_debt_hours}}', '{{sleep_regularity_proxy}}',
|
||||
'{{recent_load_balance_3d}}', '{{sleep_quality_7d}}',
|
||||
'{{correlation_sleep_recovery}}',
|
||||
],
|
||||
'vitalwerte': [
|
||||
'{{vitals_avg_hr}}', '{{vitals_avg_hrv}}', '{{vitals_vo2_max}}',
|
||||
'{{hrv_vs_baseline_pct}}', '{{rhr_vs_baseline_pct}}',
|
||||
],
|
||||
'zeitraum': [
|
||||
'{{datum_heute}}', '{{zeitraum_7d}}', '{{zeitraum_30d}}', '{{zeitraum_90d}}'
|
||||
]
|
||||
],
|
||||
'phase0b_meta': [
|
||||
'{{goal_progress_score}}', '{{data_quality_score}}',
|
||||
],
|
||||
'ziele_fokus': [
|
||||
'{{top_goal_name}}', '{{top_goal_progress_pct}}', '{{top_goal_status}}',
|
||||
'{{top_focus_area_name}}', '{{top_focus_area_progress}}',
|
||||
'{{focus_cat_körper_progress}}', '{{focus_cat_körper_weight}}',
|
||||
'{{focus_cat_ernährung_progress}}', '{{focus_cat_ernährung_weight}}',
|
||||
'{{focus_cat_aktivität_progress}}', '{{focus_cat_aktivität_weight}}',
|
||||
'{{focus_cat_recovery_progress}}', '{{focus_cat_recovery_weight}}',
|
||||
'{{focus_cat_vitalwerte_progress}}', '{{focus_cat_vitalwerte_weight}}',
|
||||
'{{focus_cat_mental_progress}}', '{{focus_cat_mental_weight}}',
|
||||
'{{focus_cat_lebensstil_progress}}', '{{focus_cat_lebensstil_weight}}',
|
||||
'{{active_goals_json}}', '{{active_goals_md}}',
|
||||
'{{focus_areas_weighted_json}}', '{{focus_areas_weighted_md}}', '{{focus_area_weights_json}}',
|
||||
'{{top_3_focus_areas}}', '{{top_3_goals_behind_schedule}}', '{{top_3_goals_on_track}}',
|
||||
],
|
||||
'korrelationen': [
|
||||
'{{correlation_energy_weight_lag}}', '{{correlation_protein_lbm}}',
|
||||
'{{correlation_load_hrv}}', '{{correlation_load_rhr}}',
|
||||
'{{plateau_detected}}', '{{top_drivers}}',
|
||||
],
|
||||
}
|
||||
|
||||
if not categories:
|
||||
|
|
@ -1460,50 +1528,7 @@ def get_placeholder_catalog(profile_id: str) -> Dict[str, List[Dict[str, str]]]:
|
|||
})
|
||||
|
||||
# Legacy placeholders (not in registry yet)
|
||||
legacy_placeholders = {
|
||||
'Profil': [
|
||||
('name', 'Name des Nutzers'),
|
||||
('age', 'Alter in Jahren'),
|
||||
('height', 'Körpergröße in cm'),
|
||||
('geschlecht', 'Geschlecht'),
|
||||
],
|
||||
'Schlaf & Erholung': [
|
||||
('sleep_avg_duration', 'Durchschn. Schlafdauer (7d)'),
|
||||
('sleep_avg_quality', 'Durchschn. Schlafqualität (7d)'),
|
||||
('rest_days_count', 'Anzahl Ruhetage (30d)'),
|
||||
('sleep_avg_duration_7d', 'Schlaf 7d (Stunden)'),
|
||||
('sleep_debt_hours', 'Schlafschuld (Stunden)'),
|
||||
('sleep_regularity_proxy', 'Schlaf-Regelmäßigkeit (Min Abweichung)'),
|
||||
('sleep_quality_7d', 'Schlafqualität 7d (0-100)'),
|
||||
],
|
||||
'Vitalwerte': [
|
||||
('vitals_avg_hr', 'Durchschn. Ruhepuls (7d)'),
|
||||
('vitals_avg_hrv', 'Durchschn. HRV (7d)'),
|
||||
('vitals_vo2_max', 'Aktueller VO2 Max'),
|
||||
('hrv_vs_baseline_pct', 'HRV vs. Baseline (%)'),
|
||||
('rhr_vs_baseline_pct', 'RHR vs. Baseline (%)'),
|
||||
],
|
||||
'Scores (Phase 0b)': [
|
||||
('goal_progress_score', 'Goal Progress Score (0-100)'),
|
||||
('recovery_score', 'Recovery Score (0-100)'),
|
||||
('data_quality_score', 'Data Quality Score (0-100)'),
|
||||
],
|
||||
'Focus Areas': [
|
||||
('top_focus_area_name', 'Top Focus Area Name'),
|
||||
('top_focus_area_progress', 'Top Focus Area Progress (%)'),
|
||||
('focus_cat_körper_progress', 'Kategorie Körper - Progress (%)'),
|
||||
('focus_cat_körper_weight', 'Kategorie Körper - Gewichtung (%)'),
|
||||
('focus_cat_ernährung_progress', 'Kategorie Ernährung - Progress (%)'),
|
||||
('focus_cat_ernährung_weight', 'Kategorie Ernährung - Gewichtung (%)'),
|
||||
('focus_cat_aktivität_progress', 'Kategorie Aktivität - Progress (%)'),
|
||||
('focus_cat_aktivität_weight', 'Kategorie Aktivität - Gewichtung (%)'),
|
||||
],
|
||||
'Zeitraum': [
|
||||
('datum_heute', 'Heutiges Datum'),
|
||||
('zeitraum_7d', '7-Tage-Zeitraum'),
|
||||
('zeitraum_30d', '30-Tage-Zeitraum'),
|
||||
],
|
||||
}
|
||||
legacy_placeholders: Dict[str, List[Tuple[str, str]]] = {}
|
||||
|
||||
# Add legacy placeholders (skip if already in registry)
|
||||
for category, items in legacy_placeholders.items():
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user