File size: 2,954 Bytes
921d377 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | """
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" # viewer | learner | customer | trainee | lead | unknown
level: str = "beginner" # beginner | intermediate | advanced
language: str = "en" # BCP-47
locale_hint: str = "" # e.g. 'italy', 'us-west'
interests: list = field(default_factory=list)
raw: Dict[str, Any] = field(default_factory=dict)
# Ordered most-specific first so 'advanced' wins over 'first time'
# in prompts like "advanced user trying this for the first time".
_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,
)
|