Spaces:
Sleeping
Sleeping
Claude
Fix Edit Personas income_level error and add missing VariantGenerator class
b4cdaab unverified | """ | |
| Persona variant generator for population sampling | |
| Creates realistic variations of base personas while maintaining core identity. | |
| """ | |
| import random | |
| from typing import List | |
| from enum import Enum | |
| from copy import deepcopy | |
| from ..personas.models import Persona, Demographics, Psychographics | |
| class VariationLevel(str, Enum): | |
| """Level of variation to apply to persona variants""" | |
| CONSERVATIVE = "conservative" # Small variations (±1-2 points) | |
| MODERATE = "moderate" # Medium variations (±2-3 points) | |
| DIVERSE = "diverse" # Large variations (±3-5 points) | |
| class PersonaVariant: | |
| """A variant of a base persona with statistical variations""" | |
| def __init__(self, base_persona: Persona, variant_id: int, variation_level: VariationLevel): | |
| """ | |
| Create a variant of a base persona | |
| Args: | |
| base_persona: The base persona to create a variant from | |
| variant_id: Unique identifier for this variant (0-99 for population of 100) | |
| variation_level: How much variation to apply | |
| """ | |
| self.base_persona = base_persona | |
| self.variant_id = variant_id | |
| self.variation_level = variation_level | |
| # Create the variant persona | |
| self.persona = self._create_variant() | |
| def _create_variant(self) -> Persona: | |
| """Create a variant persona with applied variations""" | |
| # Deep copy the base persona | |
| variant = deepcopy(self.base_persona) | |
| # Update the persona ID | |
| variant.persona_id = f"{self.base_persona.persona_id}_v{self.variant_id}" | |
| # Get variation ranges based on level | |
| ranges = self._get_variation_ranges() | |
| # Apply variations to demographics | |
| variant.demographics = self._vary_demographics( | |
| variant.demographics, ranges | |
| ) | |
| # Apply variations to psychographics | |
| variant.psychographics = self._vary_psychographics( | |
| variant.psychographics, ranges | |
| ) | |
| return variant | |
| def _get_variation_ranges(self) -> dict: | |
| """Get variation ranges based on variation level""" | |
| if self.variation_level == VariationLevel.CONSERVATIVE: | |
| return { | |
| "age": 2, | |
| "years_in_community": 1, | |
| "scalar_traits": 1, # For 1-10 scales | |
| } | |
| elif self.variation_level == VariationLevel.MODERATE: | |
| return { | |
| "age": 5, | |
| "years_in_community": 3, | |
| "scalar_traits": 2, | |
| } | |
| else: # DIVERSE | |
| return { | |
| "age": 10, | |
| "years_in_community": 5, | |
| "scalar_traits": 3, | |
| } | |
| def _vary_demographics(self, demographics: Demographics, ranges: dict) -> Demographics: | |
| """Apply variations to demographic attributes""" | |
| varied = deepcopy(demographics) | |
| # Vary age within reasonable bounds | |
| age_delta = random.randint(-ranges["age"], ranges["age"]) | |
| varied.age = max(18, min(100, demographics.age + age_delta)) | |
| # Vary years in community | |
| years_delta = random.randint(-ranges["years_in_community"], ranges["years_in_community"]) | |
| varied.years_in_community = max(0, demographics.years_in_community + years_delta) | |
| # Occasionally vary household size (20% chance) | |
| if random.random() < 0.2: | |
| varied.household_size = max(1, demographics.household_size + random.choice([-1, 1])) | |
| # Occasionally flip boolean traits (10% chance each) | |
| if random.random() < 0.1: | |
| varied.has_children = not demographics.has_children | |
| if random.random() < 0.1: | |
| varied.owns_home = not demographics.owns_home | |
| return varied | |
| def _vary_psychographics(self, psychographics: Psychographics, ranges: dict) -> Psychographics: | |
| """Apply variations to psychographic attributes""" | |
| varied = deepcopy(psychographics) | |
| # Vary scalar traits (all the 1-10 scales) | |
| trait_range = ranges["scalar_traits"] | |
| varied.openness_to_change = self._vary_scalar( | |
| psychographics.openness_to_change, trait_range | |
| ) | |
| varied.community_engagement = self._vary_scalar( | |
| psychographics.community_engagement, trait_range | |
| ) | |
| varied.environmental_concern = self._vary_scalar( | |
| psychographics.environmental_concern, trait_range | |
| ) | |
| varied.economic_focus = self._vary_scalar( | |
| psychographics.economic_focus, trait_range | |
| ) | |
| varied.social_equity_focus = self._vary_scalar( | |
| psychographics.social_equity_focus, trait_range | |
| ) | |
| varied.risk_tolerance = self._vary_scalar( | |
| psychographics.risk_tolerance, trait_range | |
| ) | |
| # Occasionally shift political leaning (15% chance) | |
| if random.random() < 0.15: | |
| varied.political_leaning = self._shift_political_leaning( | |
| psychographics.political_leaning | |
| ) | |
| return varied | |
| def _vary_scalar(self, value: int, range_val: int) -> int: | |
| """Vary a scalar value (1-10 scale) within range""" | |
| delta = random.randint(-range_val, range_val) | |
| return max(1, min(10, value + delta)) | |
| def _shift_political_leaning(self, current: str) -> str: | |
| """Shift political leaning by one step""" | |
| leanings = [ | |
| "very_progressive", | |
| "progressive", | |
| "moderate", | |
| "conservative", | |
| "very_conservative" | |
| ] | |
| if current not in leanings: | |
| return current | |
| idx = leanings.index(current) | |
| # Shift left or right by one (50/50 chance) | |
| direction = random.choice([-1, 1]) | |
| new_idx = max(0, min(len(leanings) - 1, idx + direction)) | |
| return leanings[new_idx] | |
| class VariantGenerator: | |
| """Generator for creating persona variants""" | |
| def __init__(self, base_persona: Persona, variation_level: VariationLevel): | |
| """ | |
| Initialize variant generator | |
| Args: | |
| base_persona: The base persona to create variants from | |
| variation_level: How much variation to apply | |
| """ | |
| self.base_persona = base_persona | |
| self.variation_level = variation_level | |
| self._counter = 0 | |
| def generate_variant(self, suffix: str = None) -> Persona: | |
| """ | |
| Generate a single variant | |
| Args: | |
| suffix: Optional suffix for variant ID | |
| Returns: | |
| A variant Persona object | |
| """ | |
| variant_id = self._counter if suffix is None else suffix | |
| self._counter += 1 | |
| variant_obj = PersonaVariant(self.base_persona, variant_id, self.variation_level) | |
| return variant_obj.persona | |
| def generate_variants( | |
| base_persona: Persona, | |
| population_size: int, | |
| variation_level: VariationLevel, | |
| seed: int = None | |
| ) -> List[Persona]: | |
| """ | |
| Generate a population of persona variants | |
| Args: | |
| base_persona: The base persona to create variants from | |
| population_size: Number of variants to generate | |
| variation_level: How much variation to apply | |
| seed: Random seed for reproducibility (optional) | |
| Returns: | |
| List of Persona objects representing the population | |
| """ | |
| if seed is not None: | |
| random.seed(seed) | |
| variants = [] | |
| for i in range(population_size): | |
| variant_obj = PersonaVariant(base_persona, i, variation_level) | |
| variants.append(variant_obj.persona) | |
| return variants | |