develop #59
10
backend/placeholder_registrations/__init__.py
Normal file
10
backend/placeholder_registrations/__init__.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
"""
|
||||
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
|
||||
|
||||
__all__ = ['nutrition_part_a']
|
||||
216
backend/placeholder_registrations/nutrition_part_a.py
Normal file
216
backend/placeholder_registrations/nutrition_part_a.py
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
"""
|
||||
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()
|
||||
281
backend/placeholder_registry.py
Normal file
281
backend/placeholder_registry.py
Normal file
|
|
@ -0,0 +1,281 @@
|
|||
"""
|
||||
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)
|
||||
136
backend/placeholder_registry_export.py
Normal file
136
backend/placeholder_registry_export.py
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
"""
|
||||
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
|
||||
|
|
@ -466,6 +466,24 @@ 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"]
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user