feat: Refine placeholder resolution with enhanced modifiers support
- Updated `resolve_placeholders` in `prompt_executor.py` to support combined modifiers for placeholders, allowing for more flexible output formats. - Enhanced `build_ai_placeholder_caption` in `placeholder_registry.py` to clarify the generation of AI context captions, focusing on descriptions and explanations. - Introduced new helper functions in `placeholder_resolver.py` to streamline the retrieval of descriptions and explanations for placeholders. - Modified tests to cover new functionality, ensuring accurate behavior for combined modifiers and improved placeholder resolution. These changes enhance the usability and clarity of placeholder outputs, providing users with richer contextual information.
This commit is contained in:
parent
a9a414b956
commit
4868e44882
|
|
@ -260,27 +260,24 @@ class PlaceholderRegistry:
|
||||||
|
|
||||||
def build_ai_placeholder_caption(metadata: PlaceholderMetadata, max_len: int = 400) -> str:
|
def build_ai_placeholder_caption(metadata: PlaceholderMetadata, max_len: int = 400) -> str:
|
||||||
"""
|
"""
|
||||||
Text für |d und Exportfeld ai_caption: zuerst **was** der Platzhalter misst (description),
|
Kurzerklärung / Einordnung für {{key|x}} und Exportfeld ``ai_caption`` (ohne Wert, ohne Einheit).
|
||||||
dann **Einordnung** (business_meaning oder gekürzter semantic_contract).
|
|
||||||
So ist klar, worauf sich der konkrete Wert bezieht — nicht nur eine „Meta-Bedeutung“.
|
Inhalt: business_meaning oder gekürzter semantic_contract; bei SCORE-Zeilen die 0–100-Skala.
|
||||||
|
Nicht enthalten: description (die nur bei {{key|d}} angehängt wird) und keine „Technischer Bezug: …“-Zeile.
|
||||||
"""
|
"""
|
||||||
desc = (metadata.description or "").strip()
|
desc = (metadata.description or "").strip()
|
||||||
bm = (metadata.business_meaning or "").strip()
|
bm = (metadata.business_meaning or "").strip()
|
||||||
sc = (metadata.semantic_contract or "").strip()
|
sc = (metadata.semantic_contract or "").strip()
|
||||||
|
|
||||||
chunks: List[str] = []
|
chunks: List[str] = []
|
||||||
if desc:
|
|
||||||
chunks.append(desc)
|
|
||||||
|
|
||||||
interpret = bm
|
interpret = bm
|
||||||
if not interpret and sc:
|
if not interpret and sc:
|
||||||
interpret = sc if len(sc) <= max_len else sc[: max_len - 1] + "…"
|
interpret = sc if len(sc) <= max_len else sc[: max_len - 1] + "…"
|
||||||
|
|
||||||
if interpret:
|
if interpret:
|
||||||
blob = " ".join(chunks).lower()
|
|
||||||
il = interpret.lower()
|
il = interpret.lower()
|
||||||
# Keine Dublette: gleicher Text oder lange Description bereits in der Interpretation
|
redundant = bool(
|
||||||
redundant = il in blob or (
|
|
||||||
desc
|
desc
|
||||||
and len(desc) >= 10
|
and len(desc) >= 10
|
||||||
and desc.lower() in il
|
and desc.lower() in il
|
||||||
|
|
@ -291,27 +288,10 @@ def build_ai_placeholder_caption(metadata: PlaceholderMetadata, max_len: int = 4
|
||||||
if metadata.placeholder_type == PlaceholderType.SCORE:
|
if metadata.placeholder_type == PlaceholderType.SCORE:
|
||||||
chunks.append("Skala 0–100: höher = im Modell günstiger / besser abgestimmt.")
|
chunks.append("Skala 0–100: höher = im Modell günstiger / besser abgestimmt.")
|
||||||
|
|
||||||
unit = (metadata.unit or "").strip()
|
|
||||||
if unit and metadata.placeholder_type != PlaceholderType.SCORE:
|
|
||||||
blob = " ".join(chunks).lower()
|
|
||||||
u_low = unit.lower()
|
|
||||||
# Einheit oft schon in description („… in g (30d)“, „Kalorien“) — nicht doppeln
|
|
||||||
compact_blob = blob.replace(" ", "").replace("/", "")
|
|
||||||
compact_u = u_low.replace(" ", "").replace("/", "")
|
|
||||||
unit_redundant = compact_u in compact_blob or (
|
|
||||||
"g/day" in u_low and ("g/" in blob or "gramm" in blob or " protein" in blob or " fett" in blob or " kh" in blob)
|
|
||||||
) or ("kcal" in u_low and ("kcal" in blob or "kalorien" in blob))
|
|
||||||
|
|
||||||
if (
|
|
||||||
not unit_redundant
|
|
||||||
and u_low not in ("score (0-100)", "0-100", "0–100", "dimensionless")
|
|
||||||
):
|
|
||||||
chunks.append(f"Technischer Bezug: {unit}.")
|
|
||||||
|
|
||||||
out = " ".join(c for c in chunks if c).strip()
|
out = " ".join(c for c in chunks if c).strip()
|
||||||
if len(out) > max_len + 120:
|
if len(out) > max_len + 120:
|
||||||
out = out[: max_len + 60] + "…"
|
out = out[: max_len + 60] + "…"
|
||||||
return out or desc or metadata.key
|
return out
|
||||||
|
|
||||||
|
|
||||||
# Global registry instance
|
# Global registry instance
|
||||||
|
|
|
||||||
|
|
@ -49,25 +49,46 @@ from data_layer.health_metrics import (
|
||||||
|
|
||||||
from placeholder_registry import build_ai_placeholder_caption, get_registry
|
from placeholder_registry import build_ai_placeholder_caption, get_registry
|
||||||
|
|
||||||
# {{key}} oder {{key|d}} — Modifier d hängt KI-Kontext (ai_caption) an
|
# {{key|d}} — nur description anhängen; {{key|x}} — nur Erklärung (ai_caption / Registry)
|
||||||
_PLACEHOLDER_TOKEN_RE = re.compile(
|
_PLACEHOLDER_TOKEN_RE = re.compile(
|
||||||
r"\{\{\s*([a-zA-Z0-9_]+)(?:\s*\|\s*([a-zA-Z0-9_,\s]+))?\s*\}\}"
|
r"\{\{\s*([a-zA-Z0-9_]+)(?:\s*\|\s*([a-zA-Z0-9_,\s]+))?\s*\}\}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _ai_caption_for_placeholder_key(key: str) -> Optional[str]:
|
def get_catalog_row_for_key(
|
||||||
meta = get_registry().get(key)
|
catalog: Optional[Dict[str, List[Dict[str, Any]]]], key: str
|
||||||
if meta:
|
) -> Optional[Dict[str, Any]]:
|
||||||
return build_ai_placeholder_caption(meta)
|
"""Katalogzeile zu Platzhalter-Key (key ohne {{}})."""
|
||||||
|
if not catalog:
|
||||||
|
return None
|
||||||
|
for items in catalog.values():
|
||||||
|
for item in items:
|
||||||
|
raw = item.get("key") or ""
|
||||||
|
ik = str(raw).replace("{{", "").replace("}}", "").strip()
|
||||||
|
if ik == key:
|
||||||
|
return item
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _description_for_registry_key(key: str) -> str:
|
||||||
|
meta = get_registry().get(key)
|
||||||
|
if not meta:
|
||||||
|
return ""
|
||||||
|
return (meta.description or "").strip()
|
||||||
|
|
||||||
|
|
||||||
|
def _explain_for_registry_key(key: str) -> str:
|
||||||
|
meta = get_registry().get(key)
|
||||||
|
if not meta:
|
||||||
|
return ""
|
||||||
|
return build_ai_placeholder_caption(meta).strip()
|
||||||
|
|
||||||
|
|
||||||
def format_value_with_d_modifier(value: str, catalog_row: Dict[str, Any]) -> str:
|
def format_value_with_d_modifier(value: str, catalog_row: Dict[str, Any]) -> str:
|
||||||
"""
|
"""
|
||||||
Entspricht der Prompt-Ersetzung bei {{key|d}}: „Wert — Kontext“.
|
Vorschau für Export wie {{key|d}}: „Wert — description“ (kein ai_caption).
|
||||||
Kontext: ai_caption aus dem Katalog, sonst description (wie prompt_executor).
|
|
||||||
"""
|
"""
|
||||||
cap = (catalog_row.get("ai_caption") or catalog_row.get("description") or "").strip()
|
cap = (catalog_row.get("description") or "").strip()
|
||||||
if cap:
|
if cap:
|
||||||
return f"{value} — {cap}"
|
return f"{value} — {cap}"
|
||||||
return str(value)
|
return str(value)
|
||||||
|
|
@ -1615,7 +1636,9 @@ def resolve_placeholders(template: str, profile_id: str) -> str:
|
||||||
|
|
||||||
Unterstützt Modifier wie bei der Prompt-Pipeline:
|
Unterstützt Modifier wie bei der Prompt-Pipeline:
|
||||||
- {{fat_avg}} — nur Wert
|
- {{fat_avg}} — nur Wert
|
||||||
- {{fat_avg|d}} — Wert plus KI-Kontext (business_meaning / semantic_contract aus Registry)
|
- {{fat_avg|d}} — Wert — description (kurz, token-sparend)
|
||||||
|
- {{fat_avg|x}} — nur Erklärung (business_meaning / semantic_contract, ggf. Score-Skala), ohne Wert
|
||||||
|
- {{fat_avg|d,x}} — Wert — description — Erklärung
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
template: Prompt template with placeholders
|
template: Prompt template with placeholders
|
||||||
|
|
@ -1637,11 +1660,27 @@ def resolve_placeholders(template: str, profile_id: str) -> str:
|
||||||
value = str(resolver(profile_id))
|
value = str(resolver(profile_id))
|
||||||
except Exception:
|
except Exception:
|
||||||
return f"[Fehler: {ph}]"
|
return f"[Fehler: {ph}]"
|
||||||
if "d" in mods:
|
|
||||||
cap = _ai_caption_for_placeholder_key(key)
|
want_d = "d" in mods
|
||||||
if cap:
|
want_x = "x" in mods
|
||||||
value = f"{value} — {cap}"
|
|
||||||
return value
|
if want_x and not want_d:
|
||||||
|
expl = _explain_for_registry_key(key)
|
||||||
|
return expl if expl else ""
|
||||||
|
|
||||||
|
if not want_d and not want_x:
|
||||||
|
return value
|
||||||
|
|
||||||
|
parts: List[str] = [value]
|
||||||
|
if want_d:
|
||||||
|
desc = _description_for_registry_key(key)
|
||||||
|
if desc:
|
||||||
|
parts.append(desc)
|
||||||
|
if want_x:
|
||||||
|
expl = _explain_for_registry_key(key)
|
||||||
|
if expl:
|
||||||
|
parts.append(expl)
|
||||||
|
return " — ".join(parts)
|
||||||
|
|
||||||
return _PLACEHOLDER_TOKEN_RE.sub(_repl, template)
|
return _PLACEHOLDER_TOKEN_RE.sub(_repl, template)
|
||||||
|
|
||||||
|
|
@ -1846,7 +1885,7 @@ def get_placeholder_catalog(profile_id: str) -> Dict[str, List[Dict[str, str]]]:
|
||||||
'key': key,
|
'key': key,
|
||||||
'description': description,
|
'description': description,
|
||||||
'example': str(example),
|
'example': str(example),
|
||||||
'ai_caption': description,
|
'ai_caption': '',
|
||||||
})
|
})
|
||||||
|
|
||||||
# Add ALL remaining placeholders from PLACEHOLDER_MAP that aren't categorized yet
|
# Add ALL remaining placeholders from PLACEHOLDER_MAP that aren't categorized yet
|
||||||
|
|
|
||||||
|
|
@ -12,14 +12,17 @@ import re
|
||||||
from typing import Dict, Any, Optional
|
from typing import Dict, Any, Optional
|
||||||
from db import get_db, get_cursor, r2d
|
from db import get_db, get_cursor, r2d
|
||||||
from fastapi import HTTPException
|
from fastapi import HTTPException
|
||||||
|
from placeholder_resolver import get_catalog_row_for_key
|
||||||
|
|
||||||
|
|
||||||
def resolve_placeholders(template: str, variables: Dict[str, Any], debug_info: Optional[Dict] = None, catalog: Optional[Dict] = None) -> str:
|
def resolve_placeholders(template: str, variables: Dict[str, Any], debug_info: Optional[Dict] = None, catalog: Optional[Dict] = None) -> str:
|
||||||
"""
|
"""
|
||||||
Replace {{placeholder}} with values from variables dict.
|
Replace {{placeholder}} with values from variables dict.
|
||||||
|
|
||||||
Supports modifiers:
|
Modifiers (Katalog aus get_placeholder_catalog empfohlen):
|
||||||
- {{key|d}} — angehängter KI-Kontext (ai_caption aus Katalog, sonst description; Katalog nötig)
|
- {{key|d}} — Wert — description (kurz)
|
||||||
|
- {{key|x}} — nur Erklärung (Katalogfeld ai_caption), ohne Zahlenwert
|
||||||
|
- {{key|d,x}} — Wert — description — Erklärung
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
template: String with {{key}} or {{key|modifiers}} placeholders
|
template: String with {{key}} or {{key|modifiers}} placeholders
|
||||||
|
|
@ -40,46 +43,66 @@ def resolve_placeholders(template: str, variables: Dict[str, Any], debug_info: O
|
||||||
parts = full_placeholder.split('|')
|
parts = full_placeholder.split('|')
|
||||||
key = parts[0].strip()
|
key = parts[0].strip()
|
||||||
modifiers = parts[1].strip() if len(parts) > 1 else ''
|
modifiers = parts[1].strip() if len(parts) > 1 else ''
|
||||||
|
mods = {x.strip().lower() for x in modifiers.split(",") if x.strip()}
|
||||||
|
want_d = "d" in mods
|
||||||
|
want_x = "x" in mods
|
||||||
|
|
||||||
if key in variables:
|
def _warn(msg: str):
|
||||||
value = variables[key]
|
|
||||||
# Convert dict/list to JSON string
|
|
||||||
if isinstance(value, (dict, list)):
|
|
||||||
resolved_value = json.dumps(value, ensure_ascii=False)
|
|
||||||
else:
|
|
||||||
resolved_value = str(value)
|
|
||||||
|
|
||||||
# Apply modifiers
|
|
||||||
if 'd' in modifiers:
|
|
||||||
if catalog:
|
|
||||||
caption = None
|
|
||||||
for cat_items in catalog.values():
|
|
||||||
matching = [item for item in cat_items if item['key'] == key]
|
|
||||||
if matching:
|
|
||||||
row = matching[0]
|
|
||||||
caption = (row.get('ai_caption') or row.get('description') or '').strip()
|
|
||||||
break
|
|
||||||
|
|
||||||
if caption:
|
|
||||||
resolved_value = f"{resolved_value} — {caption}"
|
|
||||||
else:
|
|
||||||
# Catalog not available - log warning in debug
|
|
||||||
if debug_info is not None:
|
|
||||||
if 'warnings' not in debug_info:
|
|
||||||
debug_info['warnings'] = []
|
|
||||||
debug_info['warnings'].append(f"Modifier |d used but catalog not available for {key}")
|
|
||||||
|
|
||||||
# Track resolution for debug
|
|
||||||
if debug_info is not None:
|
if debug_info is not None:
|
||||||
resolved[key] = resolved_value[:100] + ('...' if len(resolved_value) > 100 else '')
|
debug_info.setdefault("warnings", []).append(msg)
|
||||||
|
|
||||||
return resolved_value
|
row = get_catalog_row_for_key(catalog, key) if catalog else None
|
||||||
else:
|
|
||||||
# Keep placeholder if no value found
|
if want_x and not want_d:
|
||||||
|
if key not in variables:
|
||||||
|
if debug_info is not None:
|
||||||
|
unresolved.append(key)
|
||||||
|
return match.group(0)
|
||||||
|
expl = (row.get("ai_caption") or "").strip() if row else ""
|
||||||
|
if not expl and catalog is None:
|
||||||
|
_warn(f"Modifier |x für {key}: Katalog fehlt (ai_caption).")
|
||||||
|
out = expl
|
||||||
|
if debug_info is not None:
|
||||||
|
resolved[key] = out[:100] + ("..." if len(out) > 100 else "")
|
||||||
|
return out
|
||||||
|
|
||||||
|
if key not in variables:
|
||||||
if debug_info is not None:
|
if debug_info is not None:
|
||||||
unresolved.append(key)
|
unresolved.append(key)
|
||||||
return match.group(0)
|
return match.group(0)
|
||||||
|
|
||||||
|
value = variables[key]
|
||||||
|
if isinstance(value, (dict, list)):
|
||||||
|
resolved_value = json.dumps(value, ensure_ascii=False)
|
||||||
|
else:
|
||||||
|
resolved_value = str(value)
|
||||||
|
|
||||||
|
if not want_d and not want_x:
|
||||||
|
out = resolved_value
|
||||||
|
if debug_info is not None:
|
||||||
|
resolved[key] = out[:100] + ("..." if len(out) > 100 else "")
|
||||||
|
return out
|
||||||
|
|
||||||
|
parts = [resolved_value]
|
||||||
|
if want_d:
|
||||||
|
if row:
|
||||||
|
desc = (row.get("description") or "").strip()
|
||||||
|
if desc:
|
||||||
|
parts.append(desc)
|
||||||
|
else:
|
||||||
|
_warn(f"Modifier |d für {key}: Katalog fehlt (description).")
|
||||||
|
if want_x:
|
||||||
|
expl = (row.get("ai_caption") or "").strip() if row else ""
|
||||||
|
if expl:
|
||||||
|
parts.append(expl)
|
||||||
|
elif catalog is not None:
|
||||||
|
_warn(f"Modifier |x (mit |d) für {key}: ai_caption leer.")
|
||||||
|
|
||||||
|
out = " — ".join(parts)
|
||||||
|
if debug_info is not None:
|
||||||
|
resolved[key] = out[:100] + ("..." if len(out) > 100 else "")
|
||||||
|
return out
|
||||||
|
|
||||||
result = re.sub(r'\{\{([^}]+)\}\}', replacer, template)
|
result = re.sub(r'\{\{([^}]+)\}\}', replacer, template)
|
||||||
|
|
||||||
# Store debug info
|
# Store debug info
|
||||||
|
|
@ -464,7 +487,7 @@ async def execute_prompt_with_data(
|
||||||
'today': datetime.now().strftime('%Y-%m-%d')
|
'today': datetime.now().strftime('%Y-%m-%d')
|
||||||
}
|
}
|
||||||
|
|
||||||
# Load placeholder catalog for |d modifier support
|
# Load placeholder catalog for |d / |x Modifier
|
||||||
try:
|
try:
|
||||||
catalog = get_placeholder_catalog(profile_id)
|
catalog = get_placeholder_catalog(profile_id)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,8 @@ from models import (
|
||||||
PromptCreate, PromptUpdate, PromptGenerateRequest,
|
PromptCreate, PromptUpdate, PromptGenerateRequest,
|
||||||
PipelineConfigCreate, PipelineConfigUpdate
|
PipelineConfigCreate, PipelineConfigUpdate
|
||||||
)
|
)
|
||||||
|
from prompt_executor import resolve_placeholders as resolve_prompt_placeholders
|
||||||
from placeholder_resolver import (
|
from placeholder_resolver import (
|
||||||
resolve_placeholders,
|
|
||||||
get_unknown_placeholders,
|
get_unknown_placeholders,
|
||||||
get_placeholder_example_values,
|
get_placeholder_example_values,
|
||||||
format_value_with_d_modifier,
|
format_value_with_d_modifier,
|
||||||
|
|
@ -432,7 +432,13 @@ def preview_prompt(data: dict, session: dict=Depends(require_auth)):
|
||||||
template = data.get('template', '')
|
template = data.get('template', '')
|
||||||
profile_id = session['profile_id']
|
profile_id = session['profile_id']
|
||||||
|
|
||||||
resolved = resolve_placeholders(template, profile_id)
|
catalog = get_placeholder_catalog(profile_id)
|
||||||
|
processed = get_placeholder_example_values(profile_id)
|
||||||
|
variables = {
|
||||||
|
k.replace('{{', '').replace('}}', ''): v
|
||||||
|
for k, v in processed.items()
|
||||||
|
}
|
||||||
|
resolved = resolve_prompt_placeholders(template, variables, None, catalog)
|
||||||
unknown = get_unknown_placeholders(template)
|
unknown = get_unknown_placeholders(template)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -458,8 +464,8 @@ def export_placeholder_values(session: dict = Depends(require_auth)):
|
||||||
"""
|
"""
|
||||||
Export all available placeholders with their current resolved values.
|
Export all available placeholders with their current resolved values.
|
||||||
|
|
||||||
Pro Zeile: value = Rohwert wie bei {{key}}, example = Vorschau wie bei {{key|d}}
|
Pro Zeile: value = {{key}}, example = Vorschau {{key|d}} (Wert — description),
|
||||||
(Wert — ai_caption bzw. description). JSON-Download für das aktive Profil.
|
ai_caption = Text für {{key|x}} (Erklärung ohne Wert). JSON für das aktive Profil.
|
||||||
"""
|
"""
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
profile_id = session['profile_id']
|
profile_id = session['profile_id']
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
"""Tests für {{key|d}}, ai_caption und Unbekannt-Erkennung."""
|
"""Tests für {{key|d}}, {{key|x}}, ai_caption und Unbekannt-Erkennung."""
|
||||||
from placeholder_registry import (
|
from placeholder_registry import (
|
||||||
PlaceholderMetadata,
|
PlaceholderMetadata,
|
||||||
PlaceholderType,
|
PlaceholderType,
|
||||||
|
|
@ -7,9 +7,10 @@ from placeholder_registry import (
|
||||||
)
|
)
|
||||||
import placeholder_resolver as pr
|
import placeholder_resolver as pr
|
||||||
from placeholder_resolver import format_value_with_d_modifier
|
from placeholder_resolver import format_value_with_d_modifier
|
||||||
|
from prompt_executor import resolve_placeholders
|
||||||
|
|
||||||
|
|
||||||
def test_build_ai_caption_prefers_business_meaning():
|
def test_build_ai_caption_is_explanation_only():
|
||||||
m = PlaceholderMetadata(
|
m = PlaceholderMetadata(
|
||||||
key="test_x",
|
key="test_x",
|
||||||
category="Test",
|
category="Test",
|
||||||
|
|
@ -23,11 +24,11 @@ def test_build_ai_caption_prefers_business_meaning():
|
||||||
output_type=OutputType.NUMERIC,
|
output_type=OutputType.NUMERIC,
|
||||||
)
|
)
|
||||||
cap = build_ai_placeholder_caption(m)
|
cap = build_ai_placeholder_caption(m)
|
||||||
assert cap.startswith("Kurzbeschreibung")
|
|
||||||
assert "Kernbedeutung" in cap
|
assert "Kernbedeutung" in cap
|
||||||
|
assert "Kurzbeschreibung" not in cap
|
||||||
|
|
||||||
|
|
||||||
def test_build_ai_caption_description_then_meaning_like_protein_avg():
|
def test_build_ai_caption_protein_avg_no_description_prefix():
|
||||||
m = PlaceholderMetadata(
|
m = PlaceholderMetadata(
|
||||||
key="protein_avg",
|
key="protein_avg",
|
||||||
category="Ernährung",
|
category="Ernährung",
|
||||||
|
|
@ -40,8 +41,8 @@ def test_build_ai_caption_description_then_meaning_like_protein_avg():
|
||||||
output_type=OutputType.NUMERIC,
|
output_type=OutputType.NUMERIC,
|
||||||
)
|
)
|
||||||
cap = build_ai_placeholder_caption(m)
|
cap = build_ai_placeholder_caption(m)
|
||||||
assert cap.startswith("Durchschn. Protein in g (30d)")
|
assert cap.startswith("Zentraler Placeholder")
|
||||||
assert "Muskelerhalt" in cap
|
assert "Durchschn. Protein" not in cap
|
||||||
assert "Technischer Bezug" not in cap
|
assert "Technischer Bezug" not in cap
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -69,6 +70,8 @@ def test_placeholder_token_regex_optional_modifier():
|
||||||
assert m1 and m1.group(1) == "fat_avg" and m1.group(2).strip() == "d"
|
assert m1 and m1.group(1) == "fat_avg" and m1.group(2).strip() == "d"
|
||||||
m2 = pr._PLACEHOLDER_TOKEN_RE.search("{{ protein_avg | d }}")
|
m2 = pr._PLACEHOLDER_TOKEN_RE.search("{{ protein_avg | d }}")
|
||||||
assert m2 and m2.group(1) == "protein_avg" and m2.group(2).strip() == "d"
|
assert m2 and m2.group(1) == "protein_avg" and m2.group(2).strip() == "d"
|
||||||
|
m3 = pr._PLACEHOLDER_TOKEN_RE.search("{{k|d,x}}")
|
||||||
|
assert m3 and m3.group(1) == "k" and m3.group(2).strip() == "d,x"
|
||||||
|
|
||||||
|
|
||||||
def test_get_unknown_placeholders_strips_modifier():
|
def test_get_unknown_placeholders_strips_modifier():
|
||||||
|
|
@ -76,17 +79,26 @@ def test_get_unknown_placeholders_strips_modifier():
|
||||||
assert set(unk) == {"not_a_real_key"}
|
assert set(unk) == {"not_a_real_key"}
|
||||||
|
|
||||||
|
|
||||||
def test_format_value_with_d_modifier_matches_prompt_executor():
|
def test_format_value_with_d_modifier_uses_description_only():
|
||||||
row = {
|
row = {
|
||||||
"key": "protein_avg",
|
"key": "protein_avg",
|
||||||
"description": "Durchschn. Protein in g (30d)",
|
"description": "Durchschn. Protein in g (30d)",
|
||||||
"example": "119g/Tag",
|
"ai_caption": "Nur für |x",
|
||||||
"ai_caption": "Durchschn. Protein in g (30d). Zentral für Muskelerhalt.",
|
|
||||||
}
|
}
|
||||||
out = format_value_with_d_modifier("119g/Tag", row)
|
out = format_value_with_d_modifier("119g/Tag", row)
|
||||||
assert out == "119g/Tag — Durchschn. Protein in g (30d). Zentral für Muskelerhalt."
|
assert out == "119g/Tag — Durchschn. Protein in g (30d)"
|
||||||
|
assert "Nur für |x" not in out
|
||||||
|
|
||||||
|
|
||||||
def test_format_value_with_d_modifier_falls_back_to_description():
|
def test_format_value_with_d_modifier_falls_back_to_description():
|
||||||
row = {"description": "Nur Beschreibung", "key": "x"}
|
row = {"description": "Nur Beschreibung", "key": "x"}
|
||||||
assert format_value_with_d_modifier("42", row) == "42 — Nur Beschreibung"
|
assert format_value_with_d_modifier("42", row) == "42 — Nur Beschreibung"
|
||||||
|
|
||||||
|
|
||||||
|
def test_prompt_executor_modifiers_d_x_combined():
|
||||||
|
catalog = {"E": [{"key": "p", "description": "Desc", "ai_caption": "Expl"}]}
|
||||||
|
v = {"p": "99"}
|
||||||
|
assert resolve_placeholders("{{p|d}}", v, None, catalog) == "99 — Desc"
|
||||||
|
assert resolve_placeholders("{{p|x}}", v, None, catalog) == "Expl"
|
||||||
|
assert resolve_placeholders("{{p|d,x}}", v, None, catalog) == "99 — Desc — Expl"
|
||||||
|
assert resolve_placeholders("{{p}}", v, None, catalog) == "99"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user