File size: 3,417 Bytes
79df050
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
from __future__ import annotations

from dataclasses import dataclass
from datetime import datetime, timezone
from typing import Any, Dict, Optional


@dataclass(frozen=True)
class EnvironmentInjection:
    summary_text: str
    raw_context: Dict[str, Any]
    metadata: Dict[str, Any]


class EnvironmentContextBuilder:
    """
    將最新環境快照轉成固定可注入格式。

    設計目標:
    1. 每回合固定注入,但內容結構固定,避免 prompt 漂移
    2. 同時保留 raw context,供工具與 runtime 做更細緻決策
    3. 缺資料時明確標記 freshness / completeness,而不是靜默忽略
    """

    def build(
        self,
        env_context: Optional[Dict[str, Any]],
        *,
        now: Optional[datetime] = None,
    ) -> EnvironmentInjection:
        ctx = dict(env_context or {})
        current_time = now or datetime.now(timezone.utc)

        lines = [
            f"snapshot_time_utc: {current_time.isoformat()}",
            f"has_location: {self._has_location(ctx)}",
            f"has_address: {bool(ctx.get('detailed_address') or ctx.get('address_display') or ctx.get('label'))}",
            f"has_timezone: {bool(ctx.get('tz'))}",
            f"has_locale: {bool(ctx.get('locale'))}",
        ]

        if ctx.get("detailed_address"):
            lines.append(f"detailed_address: {ctx['detailed_address']}")
        elif ctx.get("address_display"):
            lines.append(f"address_display: {ctx['address_display']}")
        elif ctx.get("label"):
            lines.append(f"label: {ctx['label']}")

        if ctx.get("city"):
            lines.append(f"city: {ctx['city']}")
        if ctx.get("admin"):
            lines.append(f"admin: {ctx['admin']}")
        if ctx.get("country_code"):
            lines.append(f"country_code: {ctx['country_code']}")
        if ctx.get("precision"):
            lines.append(f"precision: {ctx['precision']}")
        if ctx.get("poi_label"):
            lines.append(f"poi_label: {ctx['poi_label']}")
        if ctx.get("road"):
            lines.append(f"road: {ctx['road']}")
        if ctx.get("house_number"):
            lines.append(f"house_number: {ctx['house_number']}")
        if ctx.get("tz"):
            lines.append(f"timezone: {ctx['tz']}")
        if ctx.get("locale"):
            lines.append(f"locale: {ctx['locale']}")
        if ctx.get("heading_cardinal"):
            lines.append(f"heading: {ctx['heading_cardinal']}")
        elif ctx.get("heading_deg") is not None:
            lines.append(f"heading_deg: {ctx['heading_deg']}")
        if ctx.get("accuracy_m") is not None:
            lines.append(f"accuracy_m: {ctx['accuracy_m']}")
        if ctx.get("lat") is not None and ctx.get("lon") is not None:
            lines.append(f"coordinates: {ctx['lat']},{ctx['lon']}")

        metadata = {
            "source": "environment_context_service",
            "freshness": "latest_available" if ctx else "missing",
            "has_location": self._has_location(ctx),
            "has_timezone": bool(ctx.get("tz")),
            "has_locale": bool(ctx.get("locale")),
        }

        return EnvironmentInjection(
            summary_text="\n".join(lines),
            raw_context=ctx,
            metadata=metadata,
        )

    @staticmethod
    def _has_location(ctx: Dict[str, Any]) -> bool:
        return ctx.get("lat") is not None and ctx.get("lon") is not None