Source code for sim_panel.decisions.types
from __future__ import annotations
from dataclasses import dataclass
from typing import Any, Dict, List, Optional, Sequence
[docs]
@dataclass(frozen=True)
class SelectionContext:
"""
Context used to render a selection prompt.
products_shown should already reflect the exposure policy (choice_set), and each item
should include a stable product_id plus panelist-facing product_display.
"""
panelist_id: str
t: int
products_shown: List[Dict[str, Any]] # each: {"product_id": str, "product_display": str, ...}
extra: Optional[Dict[str, Any]] = None
[docs]
@dataclass(frozen=True)
class SelectionResult:
"""
Parsed selection output.
requested_product_ids are what the panelist asked to evaluate (free will).
The generator may later apply execution rules to decide what to actually execute.
"""
requested_product_ids: List[str]
traces: Optional[Dict[str, Any]] = None
raw_text: Optional[str] = None
errors: Optional[List[str]] = None
[docs]
@dataclass(frozen=True)
class SelectionConfig:
"""
YAML-governed config for selection prompting/parsing.
If allow_empty is False and selection output is empty (after filtering),
generator can decide a fallback (re-prompt or force one).
"""
allow_empty: bool = True
include_features: bool = True
# LLM response constraints
require_json_only: bool = True
max_selected_soft: Optional[int] = None # operational hint in prompt; not a hard constraint
# Include raw text in SelectionResult (debugging)
include_raw_text: bool = True
# Optional few-shot exemplar override used only when prompting_strategy == "few_shot".
# If absent, built-in few-shot prompting should remain general.
#
# Expected shape:
# {
# "intro": "Given products about craft beverages, a respondent might select:",
# "response": {
# "selected_product_ids": ["__FIRST_SHOWN__", "__THIRD_SHOWN_IF_AVAILABLE__"],
# "traces": {"reasoning": "Selected based on personal taste preferences."}
# }
# }
#
# selected_product_ids may contain either literal product_ids or reserved placeholders
# to be resolved against ctx.products_shown at render time.
custom_few_shot_example: Optional[Dict[str, Any]] = None
[docs]
@dataclass(frozen=True)
class ExecutionRules:
"""
Generator-side operational rules (NOT panelist constraints).
Applied after parsing requested_product_ids:
- subset filtering
- optional cap
- empty fallback behavior
"""
enforce_subset_of_choice_set: bool = True
max_evals_per_panelist_per_t: Optional[int] = None # None => unlimited
allow_empty: bool = True # if False, generator should choose a fallback if empty
# If max_evals is set, keep strategy determines which items survive.
# For v0: keep_first preserves the panelist's order.
keep_strategy: str = "keep_first" # future: "random", "rerank", etc.