#!/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()
To embed this project on your website, copy the following code and paste it into your website's HTML: