Spaces:
Running
Running
| """ | |
| Social Caching ์ ํธ๋ฆฌํฐ | |
| ๋ค๋ฅธ ์ฌ์ฉ์๊ฐ ์์ฑํ ์ธ๊ธฐ ์ฝ์ค๋ฅผ ์ฌ์ฌ์ฉํ์ฌ AI ๋น์ฉ ์ ๊ฐ. | |
| ์บ์ ํํธ ์ 0.5์ด ์ด๋ด ์๋ต ๋ชฉํ. | |
| @module cache_utils | |
| @description | |
| - ์ฝ์ค ์์ฑ ํ๋ผ๋ฏธํฐ๋ฅผ ํด์๋ก ๋ณํ | |
| - ์ ์ฌํ ์กฐ๊ฑด์ ์ฝ์ค๋ฅผ ๋น ๋ฅด๊ฒ ๋งค์นญ | |
| - ์์น๋ ์ฝ 1km ๋ฐ๊ฒฝ์ผ๋ก ๊ทธ๋ฃนํ | |
| - ์๊ฐ์ 10๋ถ ๋จ์๋ก ๋ฐ์ฌ๋ฆผ | |
| @changelog | |
| - v1.1.0 (2026-01-26): ํด์ ๊ธธ์ด ์ฆ๊ฐ (L001) | |
| - 16์๋ฆฌ -> 32์๋ฆฌ๋ก ๋ณ๊ฒฝ | |
| - ์ถฉ๋ ํ๋ฅ ๊ฐ์: 2^64 -> 2^128 ๊ฐ๋ฅ ์กฐํฉ | |
| - v1.0.0 (2026-01-25): ์ด๊ธฐ ๊ตฌํ | |
| """ | |
| import hashlib | |
| import json | |
| from typing import Dict, Any, Optional, List | |
| def create_params_hash( | |
| theme: Optional[str], | |
| duration_minutes: int, | |
| location_lat: float, | |
| location_lng: float, | |
| activity_level: Optional[str] = None, | |
| mood: Optional[List[str]] = None | |
| ) -> str: | |
| """ | |
| ์ฝ์ค ์์ฑ ํ๋ผ๋ฏธํฐ๋ฅผ ํด์๋ก ๋ณํ | |
| ์ ์ฌํ ์กฐ๊ฑด์ ์ฝ์ค๋ฅผ ๋งค์นญํ๊ธฐ ์ํด ํ๋ผ๋ฏธํฐ๋ฅผ ์ ๊ทํํ์ฌ ํด์ ์์ฑ. | |
| - ์๊ฐ: 10๋ถ ๋จ์๋ก ๋ฐ์ฌ๋ฆผ (55๋ถ โ 60๋ถ, 65๋ถ โ 70๋ถ) | |
| - ์์น: ์์์ 2์๋ฆฌ๋ก ๋ฐ์ฌ๋ฆผ (์ฝ 1km ๋ฐ๊ฒฝ ๊ทธ๋ฃนํ) | |
| - mood: ์ ๋ ฌํ์ฌ ์์ ๋ฌด๊ดํ๊ฒ ๋์ผ ํด์ ์์ฑ | |
| Args: | |
| theme: ํ ๋ง (history, nature, food, photo, healing) | |
| duration_minutes: ํฌ๋ง ์๊ฐ (๋ถ) | |
| location_lat: ์ฌ์ฉ์ ์๋ | |
| location_lng: ์ฌ์ฉ์ ๊ฒฝ๋ | |
| activity_level: ํ๋ ์์ค (light, moderate, active) (optional) | |
| mood: ๋ถ์๊ธฐ ๋ฆฌ์คํธ (quiet, vibrant, romantic, family) (optional) | |
| Returns: | |
| SHA256 ํด์ (32์๋ฆฌ) - ์: "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6" | |
| Examples: | |
| >>> create_params_hash("history", 60, 33.45, 126.32) | |
| 'a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6' | |
| >>> create_params_hash("nature", 55, 33.456, 126.321) # ์๊ฐ 55โ60, ์์น ๋ฐ์ฌ๋ฆผ | |
| 'b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7' | |
| >>> create_params_hash("food", 90, 33.45, 126.32, mood=["romantic", "quiet"]) | |
| >>> create_params_hash("food", 90, 33.45, 126.32, mood=["quiet", "romantic"]) # ๋์ผ ํด์ | |
| """ | |
| # ์๊ฐ์ 10๋ถ ๋จ์๋ก ๋ฐ์ฌ๋ฆผ | |
| # 55๋ถ โ 60๋ถ, 65๋ถ โ 70๋ถ, 30๋ถ โ 30๋ถ | |
| duration_rounded = round(duration_minutes / 10) * 10 | |
| # ์์น๋ฅผ ์ฝ 1km ๋ฐ๊ฒฝ์ผ๋ก ๊ทธ๋ฃนํ (์์์ 2์๋ฆฌ) | |
| # ์๋ 0.01๋ โ 1.1km, ๊ฒฝ๋ 0.01๋ โ 0.9km (์ ์ฃผ ๊ธฐ์ค) | |
| lat_rounded = round(location_lat, 2) | |
| lng_rounded = round(location_lng, 2) | |
| # ํด์ ๋ฐ์ดํฐ ๊ตฌ์ฑ | |
| hash_data: Dict[str, Any] = { | |
| "theme": theme or "general", | |
| "duration": duration_rounded, | |
| "lat": lat_rounded, | |
| "lng": lng_rounded, | |
| } | |
| # ์ ํ์ ํ๋ ์ถ๊ฐ (์กด์ฌํ๋ ๊ฒฝ์ฐ๋ง) | |
| if activity_level: | |
| hash_data["activity"] = activity_level | |
| if mood and len(mood) > 0: | |
| # mood ์ ๋ ฌํ์ฌ ์์ ๋ฌด๊ดํ๊ฒ ๋์ผ ํด์ ๋ณด์ฅ | |
| hash_data["mood"] = sorted(mood) | |
| # JSON ๋ฌธ์์ด๋ก ๋ณํ ํ ํด์ ์์ฑ | |
| # sort_keys=True๋ก ํค ์์ ์ผ๊ด์ฑ ๋ณด์ฅ | |
| json_str = json.dumps(hash_data, sort_keys=True, ensure_ascii=False) | |
| hash_obj = hashlib.sha256(json_str.encode('utf-8')) | |
| # 32์๋ฆฌ ํด์ ๋ฐํ (์ถฉ๋ ํ๋ฅ ๊ทนํ ๋ฎ์, 16์๋ฆฌ์์ ์ฆ๊ฐ) | |
| # 16์๋ฆฌ: 2^64 ๊ฐ๋ฅ ์กฐํฉ โ 32์๋ฆฌ: 2^128 ๊ฐ๋ฅ ์กฐํฉ | |
| HASH_LENGTH = 32 | |
| return hash_obj.hexdigest()[:HASH_LENGTH] | |
| def normalize_params_for_cache( | |
| theme: Optional[str], | |
| duration_minutes: int, | |
| location_lat: float, | |
| location_lng: float, | |
| activity_level: Optional[str] = None, | |
| mood: Optional[List[str]] = None | |
| ) -> Dict[str, Any]: | |
| """ | |
| ์บ์ ์กฐํ๋ฅผ ์ํด ํ๋ผ๋ฏธํฐ๋ฅผ ์ ๊ทํ | |
| create_params_hash์ ๋์ผํ ์ ๊ทํ ๋ก์ง์ ์ ์ฉํ์ฌ ๋๋ฒ๊น /๋ก๊น ์ฉ ๋ฐ์ดํฐ ๋ฐํ. | |
| Args: | |
| (create_params_hash์ ๋์ผ) | |
| Returns: | |
| ์ ๊ทํ๋ ํ๋ผ๋ฏธํฐ ๋์ ๋๋ฆฌ | |
| """ | |
| duration_rounded = round(duration_minutes / 10) * 10 | |
| lat_rounded = round(location_lat, 2) | |
| lng_rounded = round(location_lng, 2) | |
| result: Dict[str, Any] = { | |
| "theme": theme or "general", | |
| "duration": duration_rounded, | |
| "lat": lat_rounded, | |
| "lng": lng_rounded, | |
| } | |
| if activity_level: | |
| result["activity"] = activity_level | |
| if mood and len(mood) > 0: | |
| result["mood"] = sorted(mood) | |
| return result | |
| def calculate_cache_similarity( | |
| params1: Dict[str, Any], | |
| params2: Dict[str, Any] | |
| ) -> float: | |
| """ | |
| ๋ ํ๋ผ๋ฏธํฐ ์ธํธ์ ์ ์ฌ๋ ๊ณ์ฐ (0.0 ~ 1.0) | |
| ํด์๊ฐ ๋ค๋ฅธ ๊ฒฝ์ฐ์๋ ์ ์ฌํ ์ฝ์ค๋ฅผ ์ฐพ๊ธฐ ์ํ ๋ณด์กฐ ํจ์. | |
| ํฅํ fuzzy matching ๊ตฌํ ์ ์ฌ์ฉ ์์ . | |
| Args: | |
| params1: ์ฒซ ๋ฒ์งธ ์ ๊ทํ๋ ํ๋ผ๋ฏธํฐ | |
| params2: ๋ ๋ฒ์งธ ์ ๊ทํ๋ ํ๋ผ๋ฏธํฐ | |
| Returns: | |
| ์ ์ฌ๋ ์ ์ (0.0 = ์์ ๋ค๋ฆ, 1.0 = ์์ ์ผ์น) | |
| """ | |
| score = 0.0 | |
| max_score = 0.0 | |
| # ํ ๋ง ์ผ์น (๊ฐ์ค์น: 30%) | |
| max_score += 0.3 | |
| if params1.get("theme") == params2.get("theme"): | |
| score += 0.3 | |
| # ์๊ฐ ์ ์ฌ๋ (๊ฐ์ค์น: 25%) | |
| max_score += 0.25 | |
| d1 = params1.get("duration", 60) | |
| d2 = params2.get("duration", 60) | |
| time_diff = abs(d1 - d2) | |
| if time_diff == 0: | |
| score += 0.25 | |
| elif time_diff <= 10: | |
| score += 0.2 | |
| elif time_diff <= 20: | |
| score += 0.15 | |
| elif time_diff <= 30: | |
| score += 0.1 | |
| # ์์น ์ ์ฌ๋ (๊ฐ์ค์น: 25%) | |
| max_score += 0.25 | |
| lat_diff = abs(params1.get("lat", 0) - params2.get("lat", 0)) | |
| lng_diff = abs(params1.get("lng", 0) - params2.get("lng", 0)) | |
| dist_approx = (lat_diff ** 2 + lng_diff ** 2) ** 0.5 | |
| if dist_approx <= 0.01: # ~1km | |
| score += 0.25 | |
| elif dist_approx <= 0.02: # ~2km | |
| score += 0.2 | |
| elif dist_approx <= 0.05: # ~5km | |
| score += 0.1 | |
| # ํ๋ ์์ค ์ผ์น (๊ฐ์ค์น: 10%) | |
| max_score += 0.1 | |
| if params1.get("activity") == params2.get("activity"): | |
| score += 0.1 | |
| # ๋ถ์๊ธฐ ์ผ์น (๊ฐ์ค์น: 10%) | |
| max_score += 0.1 | |
| mood1 = set(params1.get("mood", [])) | |
| mood2 = set(params2.get("mood", [])) | |
| if mood1 and mood2: | |
| overlap = len(mood1 & mood2) | |
| total = len(mood1 | mood2) | |
| score += 0.1 * (overlap / total) if total > 0 else 0 | |
| elif not mood1 and not mood2: | |
| score += 0.1 | |
| return round(score / max_score, 2) if max_score > 0 else 0.0 | |