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,
    )