File size: 5,544 Bytes
f00d669
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c552106
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f00d669
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
"""Brick 6 tests: grounded narration + the hard 0% hallucination gate (P0-6)."""
from __future__ import annotations

import pytest

from discoverroute import config
from discoverroute.narrate import grounding
from discoverroute.narrate.narrate import template_narration


class FakePOI:
    def __init__(self, name, category):
        self.name, self.category = name, category


POIS = [
    FakePOI("Jardin des Plantes", "park_garden"),
    FakePOI("Fontaine Médicis", "water_feature"),
    FakePOI(None, "cafe"),  # unnamed -> referred to by type, not a place name
]


def test_extract_multiword_place_names():
    mentions = grounding.extract_mentions(
        "Start at Place de la République, then visit Jardin des Plantes."
    )
    joined = " | ".join(mentions)
    assert "Place de la République" in joined
    assert "Jardin des Plantes" in joined


def test_gate_passes_grounded_text():
    text = ("From Bastille, pass by Jardin des Plantes for some green, then "
            "Fontaine Médicis. Finally on to the Panthéon area.")
    ok, offenders = grounding.verify_grounded(
        text, POIS, start_label="Bastille", end_label="Panthéon area"
    )
    assert ok, offenders


def test_gate_catches_hallucination():
    text = "Pass by Jardin des Plantes, then the Eiffel Tower, a lovely detour."
    ok, offenders = grounding.verify_grounded(text, POIS, start_label="Bastille")
    assert not ok
    assert any("Eiffel" in o for o in offenders)


def test_gate_catches_appended_qualifier():
    """A real name extended with an invented qualifier must NOT pass (the
    'Café de la Paix' -> 'Café de la Paix sur Seine' hallucination vector)."""
    pois = [FakePOI("Fontaine Médicis", "water_feature")]
    text = "Pass by Fontaine Médicis sur Montmartre, a lovely spot."
    ok, offenders = grounding.verify_grounded(text, pois, start_label="Bastille")
    assert not ok
    assert any("Montmartre" in o for o in offenders)


def test_gate_allows_shortened_reference():
    """Referring to a place by a shortened form of its real name is fine."""
    pois = [FakePOI("Jardin des Plantes de Paris", "park_garden")]
    text = "Stroll through Jardin des Plantes, then onward."
    ok, offenders = grounding.verify_grounded(text, pois, start_label="Bastille")
    assert ok, offenders


def test_gate_allows_unnamed_by_type():
    text = "Stop at a cafe near Jardin des Plantes for a coffee-stop pause."
    ok, offenders = grounding.verify_grounded(text, POIS, start_label="Bastille")
    assert ok, offenders


def test_geo_gazetteer_allows_real_districts():
    """A guide may name the city's real districts/river when given the gazetteer,
    so vivid prose passes the gate instead of being thrown out for the template."""
    from discoverroute.narrate import gazetteer
    geo = gazetteer.geo_terms("paris", "Paris")
    text = ("From the Marais, drift down toward the Seine and into the Latin "
            "Quarter, passing Jardin des Plantes for some green and Fontaine "
            "Médicis. A very Parisian wander with Roman echoes near the Panthéon.")
    ok, offenders = grounding.verify_grounded(
        text, POIS, start_label="Bastille", end_label="Panthéon",
        extra_allowed=geo,
    )
    assert ok, offenders


def test_geo_gazetteer_still_blocks_invented_venue():
    """Loosening for districts must NOT let an invented venue through."""
    from discoverroute.narrate import gazetteer
    geo = gazetteer.geo_terms("paris", "Paris")
    text = ("Cross the Marais, then stop at Café des Mensonges, a charming "
            "invented spot, before Jardin des Plantes.")
    ok, offenders = grounding.verify_grounded(
        text, POIS, start_label="Bastille", extra_allowed=geo,
    )
    assert not ok
    assert any("Mensonges" in o for o in offenders)


def test_uncurated_city_has_no_extra_context():
    """An on-demand area (bbox-hash key) gets no gazetteer => fails closed."""
    from discoverroute.narrate import gazetteer
    assert gazetteer.geo_terms("48.1,2.2,48.2,2.3", "this area") == ["this area"]


class _Route:
    def __init__(self, time_min):
        self.time_min = time_min


def test_template_is_grounded_by_construction():
    plain, discovery = _Route(40), _Route(58)
    text = template_narration(
        plain, discovery, POIS, vibe="quiet green wander", mode="walk",
        start_label="Bastille", end_label="Panthéon",
        posture={"park_garden": "pass", "water_feature": "pass", "cafe": "stop"},
    )
    ok, offenders = grounding.verify_grounded(
        text, POIS, start_label="Bastille", end_label="Panthéon"
    )
    assert ok, f"template leaked place names: {offenders}"
    assert "Jardin des Plantes" in text and "Fontaine Médicis" in text


data_ready = pytest.mark.skipif(
    not (config.GRAPH_WALK_PATH.exists() and config.POIS_PATH.exists()),
    reason="Graph or POI table not built",
)


@data_ready
def test_end_to_end_narration_grounded():
    """The shipped itinerary for a real route must pass the gate (the release gate)."""
    from discoverroute.pipeline import plan_route
    r = plan_route("Place de la République, Paris", "Jardin du Luxembourg, Paris",
                   budget=0.7, vibe="quiet green wander")
    assert r.discovery is not None and r.pois
    ok, offenders = grounding.verify_grounded(
        r.itinerary_md, r.pois,
        start_label="Place de la République, Paris",
        end_label="Jardin du Luxembourg, Paris",
    )
    assert ok, f"hallucinated place names in shipped narration: {offenders}"