気分で決めよう今日のご飯!

コード

# kabuka.py
import requests
import json
import sys

# ==================== 設定 ====================
API_KEY = "ここにAPI key"  # 環境変数推奨
URL = "http://webservice.recruit.co.jp/hotpepper/gourmet/v1/"

MOOD_KEYWORDS = {
    "ガッツリ": "ステーキ ハンバーグ 肉 ラーメン 丼",
    "さっぱり": "サラダ 冷麺 魚 そば うどん",
    "豪華": "フレンチ イタリアン 寿司 割烹",
    "ヘルシー": "野菜 サラダ 豆腐 玄米 低カロリー",
    "甘い": "カフェ パンケーキ スイーツ デザート",
    "辛い": "激辛 麻婆 カレー 担々麺",
    "おしゃれ": "カフェ ワイン ビストロ",
    "安い": "定食 ラーメン 牛丼 うどん",
    "ひとり": "カウンター ラーメン そば",
}

# ==================== 座標取得 ====================
def get_lat_lng(location):
    geocode_url = "https://nominatim.openstreetmap.org/search"
    params = {"q": location, "format": "json", "limit": 1}
    headers = {"User-Agent": "LunchFinder/1.0 (contact@example.com)"}
    try:
        r = requests.get(geocode_url, params=params, headers=headers, timeout=10)
        r.raise_for_status()
        data = r.json()
        if data:
            lat = float(data[0]["lat"])
            lng = float(data[0]["lon"])
            display_name = data[0].get("display_name", "")
            return lat, lng, display_name
    except Exception as e:
        print(f"座標取得エラー: {e}", file=sys.stderr)
    return None, None, ""

# ==================== 距離自動決定 ====================
def auto_range(lat, lng, place_name):
    urban_areas = ["東京", "大阪", "名古屋", "横浜", "京都", "神戸", "札幌", "福岡", "仙台", "広島"]
    if any(city in place_name for city in urban_areas):
        return 2  # 500m
    elif any(keyword in place_name for keyword in ["駅", "町", "市", "区"]):
        return 3  # 1km
    else:
        return 5  # 3km

# ==================== 予算コード ====================
def get_budget_code(budget_input):
    if not budget_input:
        return ""
    try:
        budget_input = budget_input.replace(" ", "")
        if "-" in budget_input:
            min_b, max_b = map(int, budget_input.split("-"))
        else:
            val = int(budget_input)
            min_b, max_b = val, val + 999

        codes = []
        budget_map = {
            (0, 1000): "B001",
            (1001, 2000): "B002",
            (2001, 3000): "B003",
            (3001, 4000): "B004",
            (4001, 5000): "B005",
            (5001, 999999): "B006",
        }
        for (min_r, max_r), code in budget_map.items():
            if min_b <= max_r and max_b >= min_r:
                codes.append(code)
        return ",".join(codes) if codes else ""
    except:
        print("金額帯の形式が不正です(例: 1000-3000)。無視します。")
        return ""

# ==================== 検索(リトライ付き) ====================
def search_with_fallback(keyword, lat, lng, range_val, budget_code="", mood=""):
    base_params = {
        "key": API_KEY, "lat": lat, "lng": lng,
        "range": range_val, "order": 4, "count": 10, "format": "json"
    }

    mood_kw = MOOD_KEYWORDS.get(mood, mood) if mood else ""
    full_keyword = f"{keyword} {mood_kw}".strip()

    trials = [
        {"keyword": full_keyword, "budget": budget_code},
        {"keyword": keyword, "budget": budget_code},
        {"keyword": full_keyword, "budget": ""},
        {"keyword": keyword, "budget": "", "range": min(5, range_val + 1)},
        {"keyword": keyword, "budget": "", "range": min(5, range_val + 2)},
        {"keyword": "ランチ", "lat": 35.6812, "lng": 139.7671, "range": 3, "budget": ""},
    ]

    for i, mod in enumerate(trials):
        params = base_params.copy()
        params.update(mod)

        dist_km = {1:0.3, 2:0.5, 3:1, 4:2, 5:3}.get(params.get("range", 3), 3)
        print(f"  試行 {i+1}: '{params.get('keyword', '')}' | {dist_km}km以内")

        try:
            r = requests.get(URL, params=params, timeout=15)
            if r.status_code != 200:
                print(f"    HTTPエラー: {r.status_code}")
                continue
            data = r.json()
            results = data.get("results", {})
            if "error" in results:
                print(f"    APIエラー: {results['error'][0]['message']}")
                continue
            shops = results.get("shop", [])
            if shops:
                print(f"    成功: {len(shops)}件ヒット!")
                return shops, params
        except Exception as e:
            print(f"    リクエスト失敗: {e}")
            continue
    return [], {}

# ==================== メイン ====================
def main():
    print("=== 気分でランチ探し(距離は自動!) ===")
    
    keyword = input("場所を入力(例: 渋谷, 東京駅): ").strip()
    if not keyword:
        keyword = "横浜駅"

    budget_input = input("金額帯(例: 1000-3000, 空欄=無制限): ").strip()
    mood = input("あなたの気分は?(例: ガッツリ, さっぱり): ").strip()

    print(f"\n「{keyword}」を検索中...")
    lat, lng, place_name = get_lat_lng(keyword)
    if not lat:
        print("場所が見つかりません。横浜駅を基準にします。")
        lat, lng = 35.6812, 139.7671
        place_name = "横浜駅"

    range_val = auto_range(lat, lng, place_name)
    dist_km = {1:0.3, 2:0.5, 3:1, 4:2, 5:3}.get(range_val, 3)
    print(f"自動距離: {dist_km}km以内({place_name.split(',')[0]}基準)")

    budget_code = get_budget_code(budget_input)

    print(f"\n検索開始...")
    shops, used_params = search_with_fallback(
        keyword=keyword, lat=lat, lng=lng,
        range_val=range_val, budget_code=budget_code, mood=mood
    )

    if not shops:
        print("\n該当なし... 明日またトライ!")
        return

    used_range = used_params.get("range", range_val)
    final_dist = {1:0.3, 2:0.5, 3:1, 4:2, 5:3}.get(used_range, 3)
    print(f"\n{'='*50}")
    print(f"  結果: {len(shops)}件 | 距離: {final_dist}km以内")
    print(f"  場所: {keyword} | 気分: {mood or 'なし'}")
    print(f"{'='*50}")

    for i, shop in enumerate(shops, 1):
        name = shop.get("name", "不明")
        genre = shop.get("genre", {}).get("name", "不明")
        budget = shop.get("budget", {}).get("average", "不明")
        address = shop.get("address", "")
        distance_m = shop.get("distance", 0)
        distance_km = round(distance_m / 1000, 2) if distance_m else "不明"
        url = shop.get("urls", {}).get("pc", "")

        match_tag = ""
        if mood:
            text = f"{name} {genre}".lower()
            mood_lower = mood.lower()
            if mood_lower in text:
                match_tag = " [完璧マッチ!]"
            elif any(k in text for k in mood_lower.split()):
                match_tag = " [合いそう]"

        print(f"\n{i}. {name}{match_tag}")
        print(f"   ジャンル: {genre} | 予算: {budget}")
        print(f"   住所: {address}")
        print(f"   距離: {distance_km}km" if distance_km != "不明" else "   距離: 不明")
        if url:
            print(f"   詳細: {url}")

# ==================== 実行 ====================
if __name__ == "__main__":
    main()

できること

ヒント: APIキーは環境変数 RCRUIT_API_KEY に入れると安全!

AIをどのように使ったか

大枠はAIを使い、最初は気分を入れることができなかったが気分も自分で入れることができるようにした

自動で場所や金額を入れることができるようにもするのはAIを使った

細かい文字の調節は自分で行なった

APIは自分で取得して行いました