File size: 2,797 Bytes
526cf2e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""The answer key, derived from persona features. ONE rule, any persona.

96 personas today; 10k real Expedia rows later — same function, zero authoring cost.
Verified total and single-valued over all 96 combos (test_rules.py).
"""

from __future__ import annotations

from .personas import Persona
from .scenarios import ScenarioTruth

_ALLOWED_OFFERS: dict[str, frozenset[str]] = {
    "low": frozenset({"15% off", "10% off"}),
    "mid": frozenset({"15% off", "free breakfast"}),
    "high": frozenset({"free breakfast", "free night"}),
}


def _intent(p: Persona) -> str:
    if p.trip_type == "conference" and p.near_event:
        return "book_hotel_near_event"
    if p.trip_type in ("conference", "business"):
        return "business_trip"  # a conference with no nearby event is still a business trip
    if p.trip_type == "family":
        return "family_vacation"
    return "generic_leisure"


def _background(p: Persona) -> str:
    if p.trip_type in ("conference", "business"):
        return "hotel_exterior"
    if p.trip_type == "family":
        return "room_interior"
    return "city_skyline"


def _headline(p: Persona) -> str:
    # FIRST MATCH WINS — the order is load-bearing. 'proximity' is gated on trip_type as well as
    # near_event, which is what guarantees it is only ever CORRECT when an event actually exists.
    if p.near_event and p.trip_type in ("conference", "business"):
        return "proximity"
    if p.budget_tier == "high":
        return "boutique"
    if p.trip_type == "leisure":
        return "destination"
    return "generic"


def _cta(p: Persona) -> str:
    return "Book now" if p.device == "mobile" else "Learn more"


def truth_for(persona: Persona) -> ScenarioTruth:
    return ScenarioTruth(
        correct_intent=_intent(persona),
        correct_background=_background(persona),
        correct_headline_strategy=_headline(persona),
        correct_cta=_cta(persona),
        allowed_offers=_ALLOWED_OFFERS[persona.budget_tier],
    )


def oracle_action(persona: Persona) -> list[int]:
    """The perfect action for a persona. Used by tests, the demo, and (later) as the agent's
    upper-bound baseline. Scores exactly 1.0 by construction.

    sorted(allowed_offers)[0] — NEVER next(iter(frozenset)), whose order varies with
    PYTHONHASHSEED and would make the oracle non-deterministic across processes.
    """
    from .brief import MENUS  # local import: brief imports personas, rules imports personas

    t = truth_for(persona)
    return [
        MENUS["intent"].index(t.correct_intent),
        MENUS["headline"].index(t.correct_headline_strategy),
        MENUS["offer"].index(sorted(t.allowed_offers)[0]),
        MENUS["cta"].index(t.correct_cta),
        MENUS["background"].index(t.correct_background),
    ]