| """ |
| Audience resolution for the interactive planner. |
| |
| An ``Audience`` is the viewer profile the planner targets. Today |
| it's resolved from free-text hints in the prompt plus optional |
| explicit fields (``role``, ``level``, ``language``). Tomorrow the |
| same signature will accept CRM / profile lookups. |
| """ |
| from __future__ import annotations |
|
|
| from dataclasses import dataclass, field |
| from typing import Any, Dict, Optional |
|
|
|
|
| @dataclass(frozen=True) |
| class Audience: |
| """Resolved audience profile. |
| |
| ``level`` is coarse: 'beginner' / 'intermediate' / 'advanced' — |
| the downstream planner maps that to CEFR tiers or XP ranges as |
| appropriate. |
| """ |
|
|
| role: str = "viewer" |
| level: str = "beginner" |
| language: str = "en" |
| locale_hint: str = "" |
| interests: list = field(default_factory=list) |
| raw: Dict[str, Any] = field(default_factory=dict) |
|
|
|
|
| |
| |
| _LEVEL_KEYWORDS = { |
| "advanced": ("advanced", "expert", "pro", "fluent", "senior"), |
| "intermediate": ("some experience", "intermediate", "mid", "practicing"), |
| "beginner": ("new", "beginner", "intro", "novice", "first time", "fresh"), |
| } |
|
|
| _ROLE_KEYWORDS = { |
| "learner": ("student", "learner", "studying", "class", "school"), |
| "customer": ("customer", "buyer", "shopper", "prospect"), |
| "trainee": ("employee", "onboarding", "new hire", "staff"), |
| "lead": ("lead", "sales", "demo", "prospect"), |
| } |
|
|
|
|
| def _scan_level(text: str) -> Optional[str]: |
| low = text.lower() |
| for lvl, kws in _LEVEL_KEYWORDS.items(): |
| for kw in kws: |
| if kw in low: |
| return lvl |
| return None |
|
|
|
|
| def _scan_role(text: str) -> Optional[str]: |
| low = text.lower() |
| for role, kws in _ROLE_KEYWORDS.items(): |
| for kw in kws: |
| if kw in low: |
| return role |
| return None |
|
|
|
|
| def resolve_audience( |
| prompt: str = "", |
| *, |
| explicit: Optional[Dict[str, Any]] = None, |
| default_language: str = "en", |
| ) -> Audience: |
| """Best-effort audience resolution. |
| |
| Precedence: ``explicit`` fields beat prompt-scanned hints beat |
| built-in defaults. Returns a frozen Audience so callers can |
| safely share instances across threads. |
| """ |
| ex = dict(explicit or {}) |
| role = ex.get("role") or _scan_role(prompt) or "viewer" |
| level = ex.get("level") or _scan_level(prompt) or "beginner" |
| language = ex.get("language") or default_language |
| locale_hint = ex.get("locale_hint") or "" |
| interests = list(ex.get("interests") or []) |
| return Audience( |
| role=str(role), |
| level=str(level), |
| language=str(language), |
| locale_hint=str(locale_hint), |
| interests=interests, |
| raw=ex, |
| ) |
|
|