embed786 commited on
Commit
1300f75
·
verified ·
1 Parent(s): 114b2fd

Upload 2 files

Browse files

Reverted changes manually by re-uploading working files

Files changed (2) hide show
  1. planmate/attractions.py +53 -27
  2. planmate/config.py +11 -87
planmate/attractions.py CHANGED
@@ -2,52 +2,78 @@
2
 
3
  import requests
4
  from typing import List, Dict
5
- from .config import OPENTRIPMAP_API_KEY # Geoapify key stored under this name
6
 
7
- GEOAPIFY_BASE = "https://api.geoapify.com/v2/places"
8
-
9
- def _get(params: Dict):
10
  params = dict(params or {})
11
- params["apiKey"] = OPENTRIPMAP_API_KEY # keep the same name
12
- r = requests.get(GEOAPIFY_BASE, params=params, timeout=30)
13
  try:
14
  r.raise_for_status()
15
  except requests.HTTPError as e:
 
16
  snippet = (r.text or "")[:300]
17
- raise RuntimeError(f"Geoapify error {r.status_code}: {snippet}") from e
18
  return r.json()
19
 
20
- def get_poi_radius(lat: float, lon: float, radius=7000, categories="tourism.sights", limit=50) -> List[Dict]:
21
  return _get(
 
22
  {
23
- "categories": categories,
24
- "filter": f"circle:{lon},{lat},{radius}",
25
- "limit": limit
26
- }
27
- ).get("features", [])
 
 
 
 
28
 
29
- def enrich_pois(pois: List[Dict]) -> List[Dict]:
 
 
 
30
  out = []
31
  for p in pois:
32
- props = p.get("properties", {})
33
  item = {
34
- "name": props.get("name") or "(Unnamed)",
35
- "dist": props.get("distance"),
36
- "kinds": props.get("categories", ""),
37
- "point": {"lat": props.get("lat"), "lon": props.get("lon")},
38
- "address": props.get("formatted")
 
39
  }
 
 
 
 
 
 
 
 
 
40
  out.append(item)
41
  return out
42
 
43
  def get_attractions_and_stays(lat: float, lon: float, radius=7000, limit=40):
44
- attractions_categories = "tourism.sights,tourism.museum,historic"
45
- stays_categories = "accommodation.hotel,accommodation.hostel,accommodation.guest_house,accommodation.apartment"
 
 
 
 
 
 
 
 
 
46
 
47
- attractions = get_poi_radius(lat, lon, radius, attractions_categories, limit)
48
- stays = get_poi_radius(lat, lon, radius, stays_categories, limit=30)
49
 
50
  return {
51
- "attractions": enrich_pois(attractions),
52
- "stays": enrich_pois(stays),
53
- }
 
2
 
3
  import requests
4
  from typing import List, Dict
5
+ from .config import OPENTRIPMAP_BASE, get_opentripmap_key
6
 
7
+ def _get(path: str, params: Dict):
8
+ url = f"{OPENTRIPMAP_BASE}{path}"
 
9
  params = dict(params or {})
10
+ params["apikey"] = get_opentripmap_key()
11
+ r = requests.get(url, params=params, timeout=30)
12
  try:
13
  r.raise_for_status()
14
  except requests.HTTPError as e:
15
+ # make API errors easier to debug in Streamlit
16
  snippet = (r.text or "")[:300]
17
+ raise RuntimeError(f"OpenTripMap error {r.status_code}: {snippet}") from e
18
  return r.json()
19
 
20
+ def get_poi_radius(lat: float, lon: float, radius=7000, kinds="interesting_places", limit=50) -> List[Dict]:
21
  return _get(
22
+ "/places/radius",
23
  {
24
+ "lat": lat,
25
+ "lon": lon,
26
+ "radius": radius,
27
+ "kinds": kinds,
28
+ "format": "json",
29
+ "limit": limit,
30
+ "rate": 2,
31
+ },
32
+ )
33
 
34
+ def get_details(xid: str) -> Dict:
35
+ return _get(f"/places/xid/{xid}", {})
36
+
37
+ def enrich_pois(pois: List[Dict], fetch_details=False) -> List[Dict]:
38
  out = []
39
  for p in pois:
 
40
  item = {
41
+ "name": p.get("name") or "(Unnamed)",
42
+ "dist": p.get("dist"),
43
+ "rate": p.get("rate"),
44
+ "kinds": p.get("kinds", ""),
45
+ "xid": p.get("xid"),
46
+ "point": p.get("point", {}),
47
  }
48
+ if fetch_details and item["xid"]:
49
+ try:
50
+ det = get_details(item["xid"])
51
+ item["wikipedia"] = det.get("wikipedia")
52
+ item["url"] = det.get("url")
53
+ item["address"] = det.get("address", {})
54
+ item["otm"] = det.get("otm")
55
+ except Exception:
56
+ pass
57
  out.append(item)
58
  return out
59
 
60
  def get_attractions_and_stays(lat: float, lon: float, radius=7000, limit=40):
61
+ # Attractions remain unchanged
62
+ attractions_kinds = "interesting_places,cultural,historic,museums,architecture"
63
+
64
+ # IMPORTANT: Use taxonomy accepted by OpenTripMap.
65
+ # "accomodations" (sic) is the umbrella category; for hotels use "other_hotels", not "hotels".
66
+ # See OTM catalog: Accomodations (accomodations), Hotels (other_hotels), Hostels (hostels).
67
+ # https://dev.opentripmap.org/catalog
68
+ stays_kinds = (
69
+ "accomodations,other_hotels,hostels,apartments,guest_houses,"
70
+ "resorts,motels,villas_and_chalet,alpine_hut"
71
+ )
72
 
73
+ attractions = get_poi_radius(lat, lon, radius, attractions_kinds, limit)
74
+ stays = get_poi_radius(lat, lon, radius, stays_kinds, limit=30)
75
 
76
  return {
77
+ "attractions": enrich_pois(attractions, fetch_details=False),
78
+ "stays": enrich_pois(stays, fetch_details=False),
79
+ }
planmate/config.py CHANGED
@@ -1,27 +1,8 @@
1
  import os
2
- import time
3
- import requests
4
- from itertools import cycle
5
 
6
- # Try to load .env (local dev only, ignored in production)
7
- try:
8
- from dotenv import load_dotenv
9
- load_dotenv()
10
- except ImportError:
11
- pass
12
-
13
- # Streamlit-safe logging
14
- def log_message(msg, level="info"):
15
- try:
16
- import streamlit as st
17
- if level == "error":
18
- st.error(msg)
19
- elif level == "warning":
20
- st.warning(msg)
21
- else:
22
- st.info(msg)
23
- except ImportError:
24
- print(f"[{level.upper()}] {msg}")
25
 
26
  APP_TITLE = "PlanMate"
27
  APP_TAGLINE = "AI Powered smart trip planner"
@@ -45,17 +26,18 @@ OPENTRIPMAP_BASE = "https://api.opentripmap.com/0.1/en"
45
  # ---------- Hugging Face Secrets Configuration ----------
46
  def get_secret(key, default=None):
47
  """
48
- Get secret from environment first (production),
49
- fallback to .env for local dev.
50
  """
 
51
  value = os.getenv(key)
52
-
53
  if value is None and default is not None:
54
  return default
55
  elif value is None:
56
- log_message(f"Missing required secret: {key}", level="error")
57
- raise RuntimeError(f"Missing required secret: {key}")
58
-
 
59
  return value
60
 
61
  def get_env(key: str) -> str:
@@ -64,67 +46,9 @@ def get_env(key: str) -> str:
64
  raise RuntimeError(f"Missing required environment variable: {key}")
65
  return val
66
 
67
- # ---------- Gemini Key Rotation ----------
68
- gemini_keys = os.getenv("GEMINI_API_KEY", "").split(",")
69
- gemini_keys = [k.strip() for k in gemini_keys if k.strip()]
70
-
71
- if not gemini_keys:
72
- raise RuntimeError("No GEMINI_API_KEY found in environment!")
73
-
74
- gemini_cycle = cycle(gemini_keys)
75
- _current_gemini_key = next(gemini_cycle)
76
-
77
  def get_gemini_key():
78
- """Get the current Gemini API key"""
79
- return _current_gemini_key
80
-
81
- def rotate_gemini_key():
82
- """Rotate to the next Gemini API key (when quota exceeded)"""
83
- global _current_gemini_key
84
- _current_gemini_key = next(gemini_cycle)
85
- log_message(f"Rotated to new Gemini API key: {_current_gemini_key}", level="warning")
86
- return _current_gemini_key
87
-
88
- # ---------- Gemini Helper Function ----------
89
- def make_gemini_request(prompt, model="gemini-2.0-flash-lite", max_retries=3):
90
- """
91
- Make a Gemini API request with automatic key rotation on quota errors.
92
- """
93
- import google.generativeai as genai
94
-
95
- for attempt in range(max_retries):
96
- try:
97
- # Configure SDK with current key
98
- genai.configure(api_key=get_gemini_key())
99
-
100
- # Create the model
101
- model_instance = genai.GenerativeModel(model)
102
-
103
- # Generate response
104
- response = model_instance.generate_content(prompt)
105
- return response.text # or response.candidates, depending on usage
106
-
107
- except Exception as e:
108
- error_message = str(e)
109
-
110
- # Case 1: Quota exceeded (429)
111
- if "429" in error_message or "quota" in error_message.lower():
112
- rotate_gemini_key()
113
- time.sleep(2) # short delay before retry
114
- continue
115
-
116
- # Case 2: Network/HTTP error
117
- elif isinstance(e, requests.exceptions.RequestException):
118
- log_message(f"Network issue: {e}. Retrying...", level="warning")
119
- time.sleep(2)
120
- continue
121
-
122
- # Any other error → stop immediately
123
- raise e
124
-
125
- raise RuntimeError("Gemini request failed after rotating all keys")
126
 
127
- # ---------- Other API Keys ----------
128
  def get_amadeus_credentials():
129
  return get_secret("AMADEUS_CLIENT_ID"), get_secret("AMADEUS_CLIENT_SECRET")
130
 
 
1
  import os
2
+ from dotenv import load_dotenv
 
 
3
 
4
+ # Load .env if present (local dev convenience)
5
+ load_dotenv()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
 
7
  APP_TITLE = "PlanMate"
8
  APP_TAGLINE = "AI Powered smart trip planner"
 
26
  # ---------- Hugging Face Secrets Configuration ----------
27
  def get_secret(key, default=None):
28
  """
29
+ Get secret from Hugging Face Spaces environment or fallback to local .env
 
30
  """
31
+ # Try to get from environment first (Hugging Face Spaces)
32
  value = os.getenv(key)
33
+
34
  if value is None and default is not None:
35
  return default
36
  elif value is None:
37
+ st.error(f"Missing required secret: {key}")
38
+ st.info("Please add this secret in your Hugging Face Space settings.")
39
+ st.stop()
40
+
41
  return value
42
 
43
  def get_env(key: str) -> str:
 
46
  raise RuntimeError(f"Missing required environment variable: {key}")
47
  return val
48
 
 
 
 
 
 
 
 
 
 
 
49
  def get_gemini_key():
50
+ return get_secret("GEMINI_API_KEY")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
 
 
52
  def get_amadeus_credentials():
53
  return get_secret("AMADEUS_CLIENT_ID"), get_secret("AMADEUS_CLIENT_SECRET")
54