#!/usr/bin/env python3
"""상황 + 오늘의 날씨 기반 옷차림 추천 프로그램

- 날씨: Open-Meteo (무료, API 키 불필요)
- 외부 라이브러리 없이 표준 라이브러리만 사용합니다.
- 인터넷이 안 될 때는 기온을 직접 입력해서 사용할 수 있습니다.

사용법:
    python outfit_recommender.py
    python outfit_recommender.py --city 부산 --situation "면접"
"""

import argparse
import json
import urllib.parse
import urllib.request

# ---------------------------------------------------------------- 날씨 조회

WEATHER_CODES = {
    0: "맑음", 1: "대체로 맑음", 2: "구름 조금", 3: "흐림",
    45: "안개", 48: "안개", 51: "이슬비", 53: "이슬비", 55: "이슬비",
    61: "비", 63: "비", 65: "강한 비", 66: "얼어붙는 비", 67: "얼어붙는 비",
    71: "눈", 73: "눈", 75: "많은 눈", 77: "싸락눈",
    80: "소나기", 81: "소나기", 82: "강한 소나기",
    85: "눈 소나기", 86: "눈 소나기",
    95: "천둥번개", 96: "천둥번개(우박)", 99: "천둥번개(우박)",
}
RAIN_CODES = {51, 53, 55, 61, 63, 65, 66, 67, 80, 81, 82, 95, 96, 99}
SNOW_CODES = {71, 73, 75, 77, 85, 86}


def _get_json(url, params):
    full = f"{url}?{urllib.parse.urlencode(params)}"
    with urllib.request.urlopen(full, timeout=10) as res:
        return json.loads(res.read().decode("utf-8"))


def fetch_weather(city):
    """도시 이름으로 오늘의 날씨를 가져옵니다."""
    geo = _get_json(
        "https://[Log in to view URL]",
        {"name": city, "count": 1, "language": "ko"},
    )
    if not geo.get("results"):
        raise ValueError(f"'{city}' 위치를 찾을 수 없습니다.")
    place = geo["results"][0]

    data = _get_json(
        "https://[Log in to view URL]",
        {
            "latitude": place["latitude"],
            "longitude": place["longitude"],
            "current": "temperature_2m,apparent_temperature,weather_code,wind_speed_10m",
            "daily": ("temperature_2m_max,temperature_2m_min,"
                      "precipitation_probability_max,uv_index_max"),
            "wind_speed_unit": "ms",
            "timezone": "auto",
            "forecast_days": 1,
        },
    )
    cur, day = data["current"], data["daily"]
    return {
        "place": place["name"],
        "temp": cur["temperature_2m"],
        "feels": cur["apparent_temperature"],
        "code": cur["weather_code"],
        "wind": cur["wind_speed_10m"],
        "t_max": day["temperature_2m_max"][0],
        "t_min": day["temperature_2m_min"][0],
        "rain_prob": day["precipitation_probability_max"][0] or 0,
        "uv": day["uv_index_max"][0] or 0,
    }


def manual_weather():
    """인터넷이 안 될 때 직접 입력."""
    print("날씨 정보를 가져오지 못했습니다. 직접 입력해 주세요.")
    t = float(input("현재 기온(°C): "))
    rain = input("비/눈 소식이 있나요? (y/n): ").strip().lower() == "y"
    return {
        "place": "직접 입력", "temp": t, "feels": t, "code": 61 if rain else 0,
        "wind": 2.0, "t_max": t, "t_min": t,
        "rain_prob": 80 if rain else 0, "uv": 3,
    }


# ---------------------------------------------------------------- 상황 분류

# 스타일: (키워드 목록, 설명, 체감온도 보정값)
# 보정값: 활동량이 많으면 덥게 느끼므로 +, 가만히 있는 야외 행사는 -
STYLES = {
    "formal": (["면접", "회의", "발표", "출근", "미팅", "프레젠테이션", "출장", "비즈니스"],
               "단정한 비즈니스 룩", 0),
    "date": (["데이트", "소개팅", "기념일", "썸", "약속", "모임", "파티"],
             "깔끔하고 센스 있는 데이트 룩", 0),
    "wedding": (["결혼식", "웨딩", "돌잔치", "장례", "조문", "상견례", "하객"],
                "격식 있는 행사 룩", 0),
    "sports": (["운동", "러닝", "조깅", "헬스", "축구", "자전거", "요가", "필라테스", "농구", "테니스"],
               "활동하기 편한 스포츠 룩", 5),
    "outdoor": (["등산", "캠핑", "하이킹", "트레킹", "낚시", "산책", "여행", "소풍", "나들이"],
                "야외 활동용 기능성 룩", 3),
    "casual": ([], "편안한 데일리 캐주얼 룩", 0),
}


def classify(situation):
    for key, (words, _, _) in STYLES.items():
        if any(w in situation for w in words):
            return key
    return "casual"


# ---------------------------------------------------------------- 추천 데이터

# 온도 구간 (체감온도 기준)
BANDS = [
    (27, "hot", "매우 더움"),
    (20, "warm", "따뜻함"),
    (12, "mild", "선선함"),
    (5, "cool", "쌀쌀함"),
    (-99, "cold", "추움"),
]

# 스타일별, 구간별 (상의, 하의, 겉옷)
OUTFITS = {
    "formal": {
        "hot":  ("반팔 셔츠 또는 얇은 블라우스", "린넨 슬랙스 / 스커트", "겉옷 생략 (실내 냉방용 얇은 재킷 챙기기)"),
        "warm": ("긴팔 셔츠 / 블라우스", "슬랙스 / 스커트", "얇은 블레이저"),
        "mild": ("셔츠 + 얇은 니트", "슬랙스", "블레이저 또는 트렌치코트"),
        "cool": ("셔츠 + 니트", "울 슬랙스", "울 코트"),
        "cold": ("히트텍 + 셔츠 + 니트", "기모 슬랙스", "두꺼운 울 코트 (패딩은 정장 위에 어색할 수 있어요)"),
    },
    "date": {
        "hot":  ("깔끔한 반팔 티 / 셔츠 / 원피스", "린넨 팬츠 / 롱스커트", "겉옷 생략"),
        "warm": ("얇은 긴팔 티 / 셔츠", "면바지 / 데님", "가벼운 가디건"),
        "mild": ("니트 / 맨투맨", "데님 / 슬랙스", "자켓 또는 트렌치코트"),
        "cool": ("도톰한 니트", "울 팬츠 / 기모 데님", "코트"),
        "cold": ("히트텍 + 니트", "기모 팬츠", "롱 코트 또는 숏패딩 + 목도리"),
    },
    "wedding": {
        "hot":  ("얇은 셔츠 / 단정한 블라우스 / 원피스", "린넨 슬랙스 / 미디 스커트", "얇은 재킷 (식장 냉방 대비)"),
        "warm": ("셔츠 / 블라우스", "슬랙스 / 스커트", "블레이저"),
        "mild": ("셔츠 + 니트 베스트", "슬랙스 / 스커트", "자켓 또는 트렌치코트"),
        "cool": ("셔츠 + 니트", "울 슬랙스", "울 코트"),
        "cold": ("히트텍 + 셔츠 + 니트", "기모 슬랙스", "롱코트 (식장에서 벗기 쉬운 것)"),
    },
    "sports": {
        "hot":  ("속건성 반팔 / 민소매", "러닝 쇼츠", "겉옷 없이"),
        "warm": ("속건성 반팔", "쇼츠 또는 얇은 트레이닝 팬츠", "허리에 두를 얇은 바람막이"),
        "mild": ("속건성 긴팔", "트레이닝 팬츠 / 레깅스", "얇은 바람막이 (운동 시작 후 벗기)"),
        "cool": ("기능성 긴팔 이너", "기모 트레이닝 팬츠", "집업 져지 / 경량 패딩 조끼"),
        "cold": ("기능성 발열 이너 + 긴팔", "기모 레깅스 / 트레이닝 팬츠", "방풍 자켓 + 장갑 + 비니"),
    },
    "outdoor": {
        "hot":  ("기능성 반팔", "쿨링 반바지 / 등산 팬츠", "UV 차단 얇은 긴팔 (햇빛용)"),
        "warm": ("기능성 반팔 또는 얇은 긴팔", "등산 팬츠", "얇은 바람막이"),
        "mild": ("기능성 긴팔 + 플리스", "등산 팬츠", "방풍 자켓"),
        "cool": ("발열 이너 + 플리스", "기모 등산 팬츠", "경량 다운 / 방풍 자켓"),
        "cold": ("발열 이너 + 플리스 + 미들레이어", "기모 등산 팬츠", "패딩 + 장갑 + 넥워머 + 비니"),
    },
    "casual": {
        "hot":  ("반팔 티 / 민소매", "반바지 / 린넨 팬츠", "겉옷 생략"),
        "warm": ("반팔 또는 얇은 긴팔 티", "면바지 / 청바지", "얇은 가디건 (아침저녁용)"),
        "mild": ("맨투맨 / 니트", "청바지 / 면바지", "가벼운 자켓 / 가디건"),
        "cool": ("두툼한 니트 / 후드", "기모 청바지 / 코듀로이", "코트 / 야상"),
        "cold": ("히트텍 + 니트", "기모 바지", "두꺼운 패딩 + 목도리 + 장갑"),
    },
}


def get_band(feels):
    for threshold, key, label in BANDS:
        if feels >= threshold:
            return key, label
    return "cold", "추움"


# ---------------------------------------------------------------- 추천 로직

def recommend(situation, w):
    style_key = classify(situation)
    _, style_desc, offset = STYLES[style_key]

    # 하루 중 최저/최고 기온 평균과 현재 체감온도를 함께 고려
    day_feel = (w["feels"] * 2 + w["t_max"] + w["t_min"]) / 4
    adjusted = day_feel + offset
    band_key, band_label = get_band(adjusted)
    top, bottom, outer = OUTFITS[style_key][band_key]

    extras = []
    if w["code"] in SNOW_CODES:
        extras += ["미끄럼 방지 신발 / 방수 부츠", "눈 맞아도 괜찮은 겉옷"]
    elif w["code"] in RAIN_CODES or w["rain_prob"] >= 50:
        extras += ["우산 (또는 우비)", "방수 신발 / 젖어도 되는 신발"]
        if style_key in ("formal", "wedding"):
            extras.append("바지 밑단이 젖지 않는 길이 (또는 여분 양말)")
    elif w["rain_prob"] >= 30:
        extras.append("접이식 우산 (비 올 가능성이 조금 있어요)")

    if w["wind"] >= 8:
        extras.append("바람이 강해요: 바람막이 / 방풍 겉옷 추천")
    elif w["wind"] >= 5 and band_key in ("cool", "cold"):
        extras.append("바람이 있어 체감온도가 더 낮아요: 목도리나 넥워머")

    if w["uv"] >= 6:
        extras.append("자외선이 강해요: 선크림 + 모자 / 선글라스")

    if w["t_max"] - w["t_min"] >= 10:
        extras.append(f"일교차가 {w['t_max'] - w['t_min']:.0f}°C로 커요: 겹쳐 입고 벗기 쉬운 옷 추천")

    if band_key == "cold":
        extras.append("장갑 / 목도리 / 핫팩")
    elif band_key == "cool" and style_key not in ("sports",):
        extras.append("얇은 목도리나 스카프 (아침저녁용)")

    return {
        "style_desc": style_desc, "band_label": band_label, "feels": adjusted,
        "top": top, "bottom": bottom, "outer": outer, "extras": extras,
    }


# ---------------------------------------------------------------- 출력

def print_report(situation, w, r):
    print("\n" + "=" * 50)
    print(f"  상황: {situation}")
    print(f"  지역: {w['place']}  |  {WEATHER_CODES.get(w['code'], '알 수 없음')}")
    print(f"  현재 {w['temp']:.1f}°C (체감 {w['feels']:.1f}°C)  "
          f"최저 {w['t_min']:.0f}° / 최고 {w['t_max']:.0f}°")
    print(f"  강수확률 {w['rain_prob']:.0f}%  바람 {w['wind']:.1f}m/s  자외선 {w['uv']:.0f}")
    print("=" * 50)
    print(f"\n오늘은 '{r['band_label']}' 날씨예요. 추천 스타일: {r['style_desc']}\n")
    print(f"  상의 : {r['top']}")
    print(f"  하의 : {r['bottom']}")
    print(f"  겉옷 : {r['outer']}")
    if r["extras"]:
        print("\n  챙기면 좋은 것")
        for e in r["extras"]:
            print(f"   - {e}")
    print()


def main():
    parser = argparse.ArgumentParser(description="상황 + 날씨 기반 옷차림 추천")
    parser.add_argument("--city", help="도시 이름 (예: 서울, 부산, Tokyo)")
    parser.add_argument("--situation", help="상황 (예: 면접, 데이트, 등산)")
    args = parser.parse_args()

    city = args.city or input("지역을 입력하세요 (기본: 서울): ").strip() or "서울"
    weather = None
    try:
        weather = fetch_weather(city)
    except Exception as e:  # 네트워크 오류, 지역 없음 등
        print(f"[알림] {e}")
        weather = manual_weather()

    # 한 번만 입력받는 모드 / 반복 모드
    if args.situation:
        print_report(args.situation, weather, recommend(args.situation, weather))
        return

    print("\n상황을 입력하면 옷차림을 추천해 드려요. (종료: q)")
    print("예: 면접, 소개팅, 결혼식, 러닝, 등산, 친구랑 카페, 출근")
    while True:
        situation = input("\n오늘의 상황 > ").strip()
        if situation.lower() in ("q", "quit", "exit", "종료", ""):
            print("오늘도 멋진 하루 보내세요!")
            break
        print_report(situation, weather, recommend(situation, weather))


    main()

Embed on website

To embed this project on your website, copy the following code and paste it into your website's HTML: