|
import os |
|
import re |
|
import time |
|
import requests |
|
import numpy as np |
|
import torch |
|
import joblib |
|
import xgboost as xgb |
|
from fastapi import FastAPI, HTTPException |
|
from pydantic import BaseModel |
|
from sentence_transformers import SentenceTransformer |
|
from typing import Optional, List,Dict |
|
from transformers import BertTokenizer, BertForSequenceClassification |
|
import uvicorn |
|
import gradio as gr |
|
from threading import Thread |
|
from fastapi.middleware.cors import CORSMiddleware |
|
|
|
|
|
|
|
|
|
app = FastAPI() |
|
import os |
|
|
|
app.add_middleware( |
|
CORSMiddleware, |
|
allow_origins=["*"], |
|
allow_credentials=True, |
|
allow_methods=["*"], |
|
allow_headers=["*"], |
|
) |
|
os.environ["HF_HOME"] = "/workspace/huggingface_cache" |
|
os.environ["HF_HUB_CACHE"] = "/workspace/huggingface_cache" |
|
os.environ["TRANSFORMERS_CACHE"] = "/workspace/huggingface_cache" |
|
|
|
|
|
print("Loading SentenceTransformer model...)") |
|
model_bert = SentenceTransformer("sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2") |
|
print("Model loaded.") |
|
|
|
|
|
items = [ |
|
|
|
|
|
{ |
|
"item_id": 1, |
|
"title": "헬스 퍼스널 프로젝트", |
|
"desc": "PT 전문 코치와 함께 근력·유산소를 체계적으로 관리 (운동, 헬스)", |
|
"personality": "외향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 2, |
|
"title": "빈야사 요가 in 강릉", |
|
"desc": "강릉 바다 풍경 속에서 호흡·동작을 익히는 빈야사 요가 클래스 (운동, 요가)", |
|
"personality": "내향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 3, |
|
"title": "매트 필라테스 집중 워크숍", |
|
"desc": "매트 동작 위주로 기초 코어 힘을 기르는 소그룹 훈련 (운동, 필라테스)", |
|
"personality": "내향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 4, |
|
"title": "오픈워터 스위밍 체험", |
|
"desc": "바다나 호수에서 수영 실력을 시험하는 자연 친화형 이벤트 (운동, 수영)", |
|
"personality": "외향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 5, |
|
"title": "테니스 포핸드 마스터클래스", |
|
"desc": "포핸드 스윙·풋워크를 집중 훈련하고 미니 게임으로 실습 (운동, 테니스)", |
|
"personality": "외향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 6, |
|
"title": "골프 숏게임 특화 레슨", |
|
"desc": "어프로치·퍼팅 등 숏게임 구간을 집중적으로 연습하는 프로그램 (운동, 골프)", |
|
"personality": "내향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 7, |
|
"title": "아웃도어 클라이밍 교실", |
|
"desc": "실외 바위 지형에서 암벽등반 기초와 안전 수칙을 배우는 챌린지 (운동, 클라이밍)", |
|
"personality": "외향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 8, |
|
"title": "풋살 드리블&패스 이벤트", |
|
"desc": "축구 소규모 버전 풋살에서 전술·팀워크를 즐기는 주말 클래스 (운동, 축구)", |
|
"personality": "외향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 9, |
|
"title": "스트리트 농구 대결전", |
|
"desc": "도심 코트에서 농구 토너먼트를 열어 실력을 겨루는 액션 (운동, 농구)", |
|
"personality": "외향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 10, |
|
"title": "볼링 스핀&릴리스 테크닉", |
|
"desc": "볼링장 전문 강사와 함께 볼 스핀, 릴리스 자세를 교정 (운동, 볼링)", |
|
"personality": "내향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 11, |
|
"title": "배드민턴 기술 업 워크숍", |
|
"desc": "스매시·드롭샷·푸트워크를 체계적으로 배우는 배드민턴 교실 (운동, 배드민턴)", |
|
"personality": "외향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 12, |
|
"title": "10K 러닝 크루 레이스", |
|
"desc": "도심 10km 달리기에 함께 도전하며 기록 향상을 목표 (운동, 러닝)", |
|
"personality": "내향형, 감정형" |
|
}, |
|
|
|
|
|
{ |
|
"item_id": 13, |
|
"title": "부산 해안 로드트립", |
|
"desc": "차로 부산 해안 도로를 달리며 해산물과 해변 풍경 만끽 (여행, 국내여행)", |
|
"personality": "외향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 14, |
|
"title": "미국 서부 배낭투어", |
|
"desc": "LA·샌프란시스코·라스베이거스 등 미국 서부 도시에 떠나는 자유여행 (여행, 해외여행)", |
|
"personality": "외향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 15, |
|
"title": "설악산 백패킹 트레킹", |
|
"desc": "설악산 국립공원에서 배낭 야영하며 산세와 계곡을 체험 (여행, 백패킹)", |
|
"personality": "내향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 16, |
|
"title": "모닥불 캠핑 나이트", |
|
"desc": "텐트 피칭과 모닥불 요리로 캠핑 문화를 느끼는 하룻밤 (여행, 캠핑)", |
|
"personality": "내향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 17, |
|
"title": "홍콩 시티 골목탐방", |
|
"desc": "홍콩 소호·몽콕 등 골목 시장과 길거리 음식을 즐기는 도시여행 (여행, 도시여행)", |
|
"personality": "외향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 18, |
|
"title": "전국 맛집 로드쇼", |
|
"desc": "블로그·SNS 추천 맛집을 직접 찾아다니며 식도락을 즐기는 투어 (여행, 맛집탐방)", |
|
"personality": "외향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 19, |
|
"title": "경주 역사 문화탐방", |
|
"desc": "신라시대 유적·박물관·전통 공연을 둘러보며 문화유산을 학습 (여행, 문화탐방)", |
|
"personality": "내향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 20, |
|
"title": "지중해 요가 힐링 투어", |
|
"desc": "지중해 연안 소도시에서 요가·명상으로 몸과 마음을 재충전 (여행, 힐링여행)", |
|
"personality": "외향형, 감정형" |
|
}, |
|
|
|
|
|
{ |
|
"item_id": 21, |
|
"title": "이문열 '우리들의 일그러진 영웅' 독서토론", |
|
"desc": "한국 현대소설의 학교 폭력·권력 관계를 토론하며 생각 확장 (독서, 소설)", |
|
"personality": "내향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 22, |
|
"title": "김소월 시 낭독 살롱", |
|
"desc": "'진달래꽃' 등 한국적 정서가 깃든 시를 낭송하고 감상을 교류 (독서, 시)", |
|
"personality": "내향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 23, |
|
"title": "공지영 '즐거운 나의 집' 에세이 톡", |
|
"desc": "가족과 일상 이야기를 담은 에세이를 함께 읽고 공감 (독서, 에세이)", |
|
"personality": "내향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 24, |
|
"title": "하이 퍼포먼스 습관 만들기", |
|
"desc": "브렌든 버처드 등 자기계발서를 기반으로 아침 루틴을 실천 (독서, 자기계발)", |
|
"personality": "내향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 25, |
|
"title": "장하석 '온도계의 철학' 인문 스터디", |
|
"desc": "온도·측정의 철학적 의미를 다룬 책을 통해 사고 확장 (독서, 인문)", |
|
"personality": "내향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 26, |
|
"title": "유홍준 '나의 문화유산답사기' 역사 독해", |
|
"desc": "한국 문화유산 답사기를 읽고 현장 답사 욕구를 높이는 토론 (독서, 역사)", |
|
"personality": "내향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 27, |
|
"title": "리처드 도킨스 '이기적 유전자' 과학 세션", |
|
"desc": "유전학 이론을 대중적으로 풀어낸 도서를 함께 읽고 토론 (독서, 과학)", |
|
"personality": "내향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 28, |
|
"title": "짐 콜린스 '좋은 기업을 넘어 위대한 기업으로' 세미나", |
|
"desc": "기업 성공 사례를 분석하며 경제·경영 통찰을 얻는 과정 (독서, 경제/경영)", |
|
"personality": "내향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 29, |
|
"title": "장자 '소요유' 철학 살롱", |
|
"desc": "도가 사상을 담은 장자의 글을 읽고 자유로운 삶의 가치 논의 (독서, 철학)", |
|
"personality": "내향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 30, |
|
"title": "에드가 드가 '발레 수업' 예술책 토크", |
|
"desc": "인상주의 화가 드가의 발레 시리즈와 예술서를 같이 탐독 (독서, 예술)", |
|
"personality": "내향형, 감정형" |
|
}, |
|
|
|
|
|
{ |
|
"item_id": 31, |
|
"title": "'시간 여행자의 아내' 로맨스 스크리닝", |
|
"desc": "시간여행을 소재로 한 사랑 이야기를 함께 보고 울고 웃는 자리 (영화, 로맨스)", |
|
"personality": "외향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 32, |
|
"title": "코미디 '슈퍼배드' 웃음 파티", |
|
"desc": "'슈퍼배드' 시리즈를 감상하며 유머 코드를 분석하고 떠들썩한 웃음을 공유 (영화, 코미디)", |
|
"personality": "외향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 33, |
|
"title": "'킹스맨' 액션씬 관전", |
|
"desc": "'킹스맨' 시리즈의 액션 연출·스파이 코드를 짚어보는 상영회 (영화, 액션)", |
|
"personality": "외향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 34, |
|
"title": "'서스펙트' 스릴러 미스터리", |
|
"desc": "긴장감 넘치는 한국 스릴러 영화를 함께 감상하고 반전 요소 해석 (영화, 스릴러)", |
|
"personality": "외향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 35, |
|
"title": "'에일리언' 공포 SF 밤샘", |
|
"desc": "'에일리언' 시리즈를 몰아보며 외계 생명체 공포와 SF 설정을 토론 (영화, 공포)", |
|
"personality": "내향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 36, |
|
"title": "'그래비티' SF 토크", |
|
"desc": "우주 공간 조난 스토리를 그린 '그래비티'의 과학·연출을 분석 (영화, SF)", |
|
"personality": "외향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 37, |
|
"title": "'해리 포터' 판타지 앤딩", |
|
"desc": "호그와트 마법 세계관을 연속 상영하고 팬심을 나누는 스팟 (영화, 판타지)", |
|
"personality": "외향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 38, |
|
"title": "'인생은 아름다워' 드라마 감동", |
|
"desc": "전쟁 속 가족사랑을 그린 이탈리아 명작으로 희망 메시지 나눔 (영화, 드라마)", |
|
"personality": "외향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 39, |
|
"title": "'너의 이름은' 애니메이션 스페셜", |
|
"desc": "신카이 마코토 감독의 빛나는 작화·청춘 판타지를 감상 (영화, 애니메이션)", |
|
"personality": "외향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 40, |
|
"title": "'마이클 무어' 다큐 포럼", |
|
"desc": "'화씨 9/11' 등 사회·정치적 메시지가 담긴 다큐를 보고 토론 (영화, 다큐멘터리)", |
|
"personality": "외향형, 이성형" |
|
}, |
|
|
|
|
|
{ |
|
"item_id": 41, |
|
"title": "'젤다의 전설: 야생의 숨결' RPG 탐험", |
|
"desc": "오픈월드 명작 '젤다' 시리즈를 탐험하며 퍼즐·전투 방식을 공유 (게임, RPG)", |
|
"personality": "내향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 42, |
|
"title": "'콜 오브 듀티' FPS 워존 배틀", |
|
"desc": "밀리터리 FPS에서 팀 단위 전술을 실행하고 승리를 노리는 대전 (게임, FPS)", |
|
"personality": "외향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 43, |
|
"title": "'갓 오브 워' 액션 앤 레이지", |
|
"desc": "크레토스의 여정·스킬 콤보를 파고들며 액션게임의 재미 체험 (게임, 액션)", |
|
"personality": "외향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 44, |
|
"title": "'문명6' 전략 빌드 세션", |
|
"desc": "턴제 전략 '문명6'에서 문명별 특성과 승리 조건을 분석 (게임, 전략)", |
|
"personality": "내향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 45, |
|
"title": "'플래닛 코스터' 시뮬레이션 제작소", |
|
"desc": "놀이공원 경영 게임에서 놀이기구 배치·경영을 디자인 (게임, 시뮬레이션)", |
|
"personality": "내향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 46, |
|
"title": "'NBA 2K' 스포츠 매치", |
|
"desc": "농구게임 'NBA 2K'로 온라인·오프라인 토너먼트를 개최 (게임, 스포츠)", |
|
"personality": "외향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 47, |
|
"title": "'폴가이즈' 퍼즐 액션 배틀", |
|
"desc": "귀엽고 혼란스러운 장애물 코스로 다수 인원이 경쟁 (게임, 퍼즐)", |
|
"personality": "외향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 48, |
|
"title": "'비트세이버' 리듬 VR 체험", |
|
"desc": "VR 기기를 착용해 박자에 맞춰 블록을 베는 리듬 대결 (게임, 음악/리듬)", |
|
"personality": "내향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 49, |
|
"title": "'매직: 더 개더링' 카드 덱실험", |
|
"desc": "TCG의 원조격인 ‘매직’에서 덱 빌드와 대전 전략을 연구 (게임, 카드)", |
|
"personality": "내향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 50, |
|
"title": "'월드 오브 워크래프트' MMORPG 길드레이드", |
|
"desc": "아제로스에서 길드원이 힘을 합쳐 레이드를 진행 (게임, MMORPG)", |
|
"personality": "외향형, 이성형" |
|
}, |
|
|
|
|
|
{ |
|
"item_id": 51, |
|
"title": "코바늘 뜨개질 애착인형 DIY", |
|
"desc": "코바늘 기법으로 인형이나 소품을 만드는 초급자 대상 워크숍 (공예, 뜨개질)", |
|
"personality": "내향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 52, |
|
"title": "일본 자수 '사시코' 교실", |
|
"desc": "사시코 기법으로 앞치마·티매트를 장식하는 레슨 (공예, 자수)", |
|
"personality": "내향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 53, |
|
"title": "도자기 핸드페인팅 체험", |
|
"desc": "빚은 도자기에 직접 그림·문양을 그려 소성까지 경험 (공예, 도자기)", |
|
"personality": "내향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 54, |
|
"title": "가죽 파우치 제작 스튜디오", |
|
"desc": "가죽 재단·바느질·마감까지 소형 파우치를 만드는 실습 (공예, 가죽공예)", |
|
"personality": "내향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 55, |
|
"title": "목공예 숟가락 만들기 워크숍", |
|
"desc": "나무를 깎고 다듬어 주방용 숟가락을 만드는 기초 체험 (공예, 목공예)", |
|
"personality": "내향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 56, |
|
"title": "비즈공예 액세서리 디자인", |
|
"desc": "비즈 구슬로 팔찌·목걸이를 만들고 색감 패턴을 익히는 클래스 (공예, 비즈공예)", |
|
"personality": "내향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 57, |
|
"title": "젤 캔들 & 디퓨저 아틀리에", |
|
"desc": "투명 젤 왁스로 캔들과 디퓨저를 창작하며 향기를 디자인 (공예, 캔들/디퓨저)", |
|
"personality": "내향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 58, |
|
"title": "페이퍼크래프트 동물 피규어", |
|
"desc": "종이로 입체 동물 모형을 만들고 채색·디테일 작업 (공예, 페이퍼크래프트)", |
|
"personality": "내향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 59, |
|
"title": "마크라메 해먹 의자 만들기", |
|
"desc": "매듭 기법을 이용해 해먹 의자·월행잉 등 인테리어 소품 제작 (공예, 마크라메)", |
|
"personality": "내향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 60, |
|
"title": "레진아트 트레이 공방", |
|
"desc": "레진과 색소·글리터로 컵받침이나 작은 트레이를 만드는 작업 (공예, 레진아트)", |
|
"personality": "내향형, 이성형" |
|
}, |
|
|
|
|
|
{ |
|
"item_id": 61, |
|
"title": "보드게임 '카탄' 챌린지", |
|
"desc": "전략 보드게임 '카탄' 대회로 무역·개척 전술을 겨루는 이벤트 (기타, 보드게임)", |
|
"personality": "외향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 62, |
|
"title": "스페인어 기초 회화 교실", |
|
"desc": "hola! 간단한 문장·회화를 배워 여행·일상에서 활용 (기타, 언어학습)", |
|
"personality": "내향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 63, |
|
"title": "홈브루잉 커피 실습", |
|
"desc": "원두 고르기부터 핸드드립·에스프레소 추출까지 커피 제작 전과정 체험 (기타, 요리/음료)", |
|
"personality": "내향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 64, |
|
"title": "K-POP 댄스 커버 스튜디오", |
|
"desc": "최신 아이돌 안무를 함께 연습·촬영해보는 퍼포먼스 프로젝트 (기타, 댄스)", |
|
"personality": "외향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 65, |
|
"title": "플라워 레슨 & 꽃다발 디자인", |
|
"desc": "생화를 활용해 부케·꽃다발을 직접 만드는 교실 (기타, 플라워)", |
|
"personality": "내향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 66, |
|
"title": "드론 항공촬영 워크숍", |
|
"desc": "드론 조종법·촬영 기법·영상 편집을 배우고 실습 (기타, 드론)", |
|
"personality": "외향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 67, |
|
"title": "비건 베이커리 클래스", |
|
"desc": "달걀·버터 없이 쿠키·빵을 만드는 레시피와 노하우 학습 (기타, 요리/베이킹)", |
|
"personality": "내향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 68, |
|
"title": "애견 행동교정 & 트릭 클래스", |
|
"desc": "반려견 기본 행동교정·산책 요령·간단한 재주를 훈련 (기타, 반려동물)", |
|
"personality": "외향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 69, |
|
"title": "영화 시나리오 작법 워크숍", |
|
"desc": "시놉시스·캐릭터 구성을 배우고 단편 시나리오를 써보는 활동 (기타, 글쓰기)", |
|
"personality": "내향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 70, |
|
"title": "홈칵테일 믹솔로지 이벤트", |
|
"desc": "칵테일 재료·비율을 익혀 집에서 간단히 만들 수 있는 레시피 실습 (기타, 요리/음료)", |
|
"personality": "외향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 71, |
|
"title": "유튜브 브이로그 크리에이팅", |
|
"desc": "영상 기획·촬영·편집 노하우를 배우고 브이로그를 완성 (기타, 영상콘텐츠)", |
|
"personality": "내향형, 이성형" |
|
}, |
|
|
|
|
|
{"item_id": 73, "title": "크로스핏 열정단", "desc": "고강도 크로스핏 운동으로 체력을 단련하는 열정적 모임 (온동, 헬스장)", "personality": "외향형, 감정형"}, |
|
{"item_id": 74, "title": "명상 요가 서클", "desc": "몸의 이완과 마음의 평화를 함께 추구하는 명상 요가 모임 (운동, 요가)", "personality": "외향형, 감정형"}, |
|
{"item_id": 75, "title": "코어 트레이닝 팸", "desc": "필라테스로 코어 근력과 유연성을 키우는 그룹 (운동, 필라테스)", "personality": "외향형, 감정형"}, |
|
{"item_id": 76, "title": "파도타기 워터팀", "desc": "수영과 워터 스포츠를 함께 즐기며 체력을 키우는 모임 (운동, 수영)", "personality": "외향형, 감정형"}, |
|
{"item_id": 77, "title": "라켓 드림클럽", "desc": "테니스를 비롯한 라켓 스포츠를 같이 배우고 즐기는 동호회 (운동, 테니스)", "personality": "외향형, 감정형"}, |
|
{"item_id": 78, "title": "필드 골프 리더스", "desc": "골프 스윙 기술과 필드 매너를 함께 익히는 라운딩 팀 (운동, 골프)", "personality": "외향형, 감정형"}, |
|
{"item_id": 79, "title": "슬링 텀블러즈", "desc": "암벽등반과 슬링 트레이닝으로 색다른 운동을 체험하는 그룹 (운동, 클라이밍)", "personality": "외향형, 감정형"}, |
|
{"item_id": 80, "title": "스포츠 골든리그", "desc": "주중·주말에 축구 경기를 주기적으로 여는 커뮤니티 (운동, 축구)", "personality": "외향형, 감정형"}, |
|
{"item_id": 81, "title": "하프코트 슈터스", "desc": "농구 코트에서 함께 뛰고 실력을 배양하는 팀 (운동, 농구)", "personality": "외향형, 감정형"}, |
|
{"item_id": 82, "title": "스트라이크 러버스", "desc": "볼링 점수를 올리기 위해 함께 연구하고 즐기는 동아리 (운동, 볼링)", "personality": "외향형, 감정형"}, |
|
{"item_id": 83, "title": "스매싱 배드민턴", "desc": "배드민턴 스매시 기술과 경기를 함께 연습하는 학습 모임 (운동, 배드민턴)", "personality": "외향형, 감정형"}, |
|
{"item_id": 84, "title": "나이트 러너스", "desc": "야간 러닝을 통해 도시를 누비며 체력을 기르는 크루 (운동, 러닝)", "personality": "외향형, 감정형"}, |
|
|
|
|
|
{"item_id": 85, "title": "오지여행 탐험대", "desc": "국내 숨겨진 오지를 찾아 떠나며 모험심을 기르는 모임 (여행, 국내여행)", "personality": "외향형, 감정형"}, |
|
{"item_id": 86, "title": "글로벌 백팩 트립", "desc": "해외 배낭여행 루트를 함께 계획하고 실행하는 동호회 (여행, 해외여행)", "personality": "외향형, 감정형"}, |
|
{"item_id": 87, "title": "필드 캠핑 어드벤처", "desc": "백패킹과 캠핑을 결합해 자연에서 살아보는 체험 모임 (여행, 백패킹)", "personality": "외향형, 감정형"}, |
|
{"item_id": 88, "title": "패밀리 캠핑 잼", "desc": "가족 단위로 캠핑하며 자연 속 소통을 즐기는 그룹 (여행, 캠핑)", "personality": "외향형, 감정형"}, |
|
{"item_id": 89, "title": "골목길 투어 클럽", "desc": "도시 골목길과 역사적 공간을 함께 탐방하는 여행 서클 (여행, 도시여행)", "personality": "외향형, 감정형"}, |
|
{"item_id": 90, "title": "미식헌터 크루", "desc": "전국 맛집을 조사하고 직접 시식 투어를 떠나는 모임 (여행, 맛집탐방)", "personality": "외향형, 감정형"}, |
|
{"item_id": 91, "title": "문화유산 탐방기획사", "desc": "전통 문화유산과 축제를 기획·탐방하는 여행 동아리 (여행, 문화탐방)", "personality": "외향형, 감정형"}, |
|
{"item_id": 92, "title": "숲속 힐링 트래블", "desc": "자연 속에서 치유와 재충전을 목적으로 여행하는 그룹 (여행, 힐링여행)", "personality": "외향형, 감정형"}, |
|
|
|
|
|
{"item_id": 93, "title": "문학 리뷰 서클", "desc": "소설과 문학작품을 읽고 비평하는 책동아리 (독서, 소설)", "personality": "내향형, 감정형"}, |
|
{"item_id": 94, "title": "시인 꿈나무 모임", "desc": "서로의 시를 낭독하고 공유하는 창작 시클럽 (독서, 시)", "personality": "내향형, 감정형"}, |
|
{"item_id": 95, "title": "마음글쓰기 에세이회", "desc": "에세이로 일상과 감정을 표현하며 소통하는 글모임 (독서, 에세이)", "personality": "내향형, 이성형"}, |
|
{"item_id": 96, "title": "실천형 자기계발단", "desc": "자기계발서를 읽고 즉시 실천 계획을 세우는 스터디 (독서, 자기계발)", "personality": "내향형, 이성형"}, |
|
{"item_id": 97, "title": "깊이읽기 인문소사이어티", "desc": "인문학 텍스트를 심층 토론하며 사고력을 확장하는 그룹 (독서, 인문)", "personality": "내향형, 이성형"}, |
|
{"item_id": 98, "title": "역사공부 디스커션", "desc": "시대별 역사적 사건을 공부하고 자유롭게 논의하는 모임 (독서, 역사)", "personality": "내향형, 이성형"}, |
|
{"item_id": 99, "title": "과학탐구 북클럽", "desc": "최신 과학 서적을 중심으로 아이디어를 나누는 독서회 (독서, 과학)", "personality": "내향형, 이성형"}, |
|
{"item_id": 100, "title": "경영전략 독해팀", "desc": "경제/경영 관련 서적을 함께 분석하고 사례를 연구하는 그룹 (독서, 경제/경영)", "personality": "내향형, 이성형"}, |
|
{"item_id": 101, "title": "철학강독 세미나", "desc": "철학 원전을 읽고 핵심 개념을 토론하는 학술 모임 (독서, 철학)", "personality": "내향형, 이성형"}, |
|
{"item_id": 102, "title": "아트북 인사이트회", "desc": "예술 관련 서적을 통해 예술사와 작품 세계를 탐구하는 모임 (독서, 예술)", "personality": "내향형, 감정형"}, |
|
|
|
|
|
{"item_id": 103, "title": "낭만 영화 애호가들", "desc": "감성적인 로맨스 영화를 좋아하는 사람들의 모임 (영화, 로맨스)", "personality": "외향형, 감정형"}, |
|
{"item_id": 104, "title": "폭소 코미디 페스티벌", "desc": "웃음을 주제로 코미디 영화를 함께 보는 소모임 (영화, 코미디)", "personality": "외향형, 감정형, 이성형"}, |
|
{"item_id": 105, "title": "액션매니아즈", "desc": "박진감 넘치는 액션 영화의 매력을 해부하는 그룹 (영화, 액션)", "personality": "외향형, 감정형"}, |
|
{"item_id": 106, "title": "서스펜스 시네마분석단", "desc": "스릴러 영화 속 긴장감과 스토리를 분석하는 모임 (영화, 스릴러)", "personality": "외향형, 감정형"}, |
|
{"item_id": 107, "title": "호러매틱 스터디", "desc": "공포영화의 분위기와 연출을 집중 탐구하는 팀 (영화, 공포)", "personality": "외향형, 감정형"}, |
|
{"item_id": 108, "title": "SF 월드 디스커버리", "desc": "공상과학 영화를 보고 미래 기술과 세계관을 토론하는 모임 (영화, SF)", "personality": "외향형, 감정형"}, |
|
{"item_id": 109, "title": "판타지 이매지네이션", "desc": "환상적인 판타지 영화를 감상하며 아이디어를 공유하는 서클 (영화, 판타지)", "personality": "외향형, 감정형"}, |
|
{"item_id": 110, "title": "휴먼드라마 인사이트", "desc": "감동적인 드라마 장르 영화를 함께 보고 교훈을 찾는 모임 (영화, 드라마)", "personality": "외향형, 감정형"}, |
|
{"item_id": 111, "title": "애니 애호회", "desc": "다양한 애니메이션 영화를 정기 상영하며 감상하는 팬클럽 (영화, 애니메이션)", "personality": "외향형, 감정형"}, |
|
{"item_id": 112, "title": "다큐 리뷰 포럼", "desc": "다큐멘터리 영화를 보고 사회·환경 문제를 논의하는 모임 (영화, 다큐멘터리)", "personality": "외향형, 이성형"}, |
|
|
|
|
|
{"item_id": 113, "title": "RPG 모험단", "desc": "판타지 세계에서 함께 파티를 이뤄 모험하는 RPG 팀 (게임, RPG)", "personality": "외향형, 이성형"}, |
|
{"item_id": 114, "title": "FPS 고인물 클랜", "desc": "고난도 슈팅 기술을 연습하며 대회에도 나가는 FPS 동호회 (게임, FPS)", "personality": "외향형, 이성형"}, |
|
{"item_id": 115, "title": "액션코어 전략팀", "desc": "액션 게임의 숨겨진 테크닉과 콤보를 연구하는 그룹 (게임, 액션)", "personality": "외향형, 이성형"}, |
|
{"item_id": 116, "title": "전략모드 시뮬클럽", "desc": "전략 시뮬레이션 장르를 심도 있게 플레이·분석하는 동호회 (게임, 전략)", "personality": "외향형, 이성형"}, |
|
{"item_id": 117, "title": "시뮬레이션 마니아들", "desc": "도시건설, 경영 시뮬레이션 등 다양한 시뮬 게임을 즐기는 모임 (게임, 시뮬레이션)", "personality": "외향형, 이성형, 감정형"}, |
|
{"item_id": 118, "title": "스포츠게이머 챌린지", "desc": "축구·농구 등 스포츠게임 리그를 운영하는 커뮤니티 (게임, 스포츠)", "personality": "외향형, 이성형"}, |
|
{"item_id": 119, "title": "퍼즐 브레인홀릭", "desc": "퍼즐·두뇌게임을 함께 풀며 아이디어를 공유하는 팀 (게임, 퍼즐)", "personality": "외향형, 이성형"}, |
|
{"item_id": 120, "title": "리듬게임 오케스트라", "desc": "음악/리듬게임에서 합주하는 느낌으로 협력 플레이하는 모임 (게임, 음악/리듬)", "personality": "외향형, 이성형"}, |
|
{"item_id": 121, "title": "카드플레이 매니아", "desc": "포커·TCG 등 카드 기반 게임을 심도 있게 즐기는 동아리 (게임, 카드)", "personality": "외향형, 이성형"}, |
|
{"item_id": 122, "title": "MMORPG 연합길드", "desc": "대규모 온라인 RPG에서 길드원들과 함께 도전하는 팀 (게임, MMORPG)", "personality": "외향형, 이성형"}, |
|
|
|
|
|
{"item_id": 123, "title": "니트디자인 스터디오", "desc": "뜨개질로 패션 소품과 의류를 창작하는 워크숍 (공예, 뜨개질)", "personality": "내향형, 이성형"}, |
|
{"item_id": 124, "title": "자수 디테일연구회", "desc": "섬세한 자수 기법을 연구하며 작품을 완성하는 모임 (공예, 자수)", "personality": "내향형, 이성형"}, |
|
{"item_id": 125, "title": "도예 공예단", "desc": "도자기 공예로 식기·인테리어 소품을 제작해보는 그룹 (공예, 도자기)", "personality": "내향형, 이성형"}, |
|
{"item_id": 126, "title": "가죽공예 스킬랩", "desc": "가죽 재단부터 바느질까지 실습하고 디자인을 공유하는 모임 (공예, 가죽공예)", "personality": "내향형, 이성형"}, |
|
{"item_id": 127, "title": "목공 마스터스", "desc": "목재를 활용해 가구·장식품을 제작하며 기술을 배우는 모임 (공예, 목공예)", "personality": "내향형, 이성형"}, |
|
{"item_id": 128, "title": "비즈 디자인 팩토리", "desc": "비즈공예로 액세서리·장신구를 창작하는 교실 (공예, 비즈공예)", "personality": "내향형, 이성형"}, |
|
{"item_id": 129, "title": "캔들/디퓨저 크리에이티브", "desc": "향초와 디퓨저를 예술적으로 표현해보는 창작 스튜디오 (공예, 캔들/디퓨저)", "personality": "내향형, 이성형"}, |
|
{"item_id": 130, "title": "페이퍼아트 디자인랩", "desc": "종이 공예로 독특한 작품과 미니어처를 만드는 연구회 (공예, 페이퍼크래프트)", "personality": "내향형, 이성형"}, |
|
{"item_id": 131, "title": "마크라메 공예실", "desc": "마크라메 매듭 기법을 다양하게 시도해보는 작업실 (공예, 마크라메)", "personality": "내향형, 이성형"}, |
|
{"item_id": 132, "title": "레진아트 크리에이터즈", "desc": "레진을 활용해 장식품·소품을 디자인하고 공유하는 모임 (공예, 레진아트)", "personality": "내향형, 이성형"}, |
|
|
|
{ |
|
"item_id": 133, |
|
"title": "나이키 헬스 피트니스 챌린지", |
|
"desc": "헬스장에서 나이키 프로그램을 통해 웨이트 및 유산소를 마스터 (운동, 헬스)", |
|
"personality": "외향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 134, |
|
"title": "아쉬탕가 요가 클래스 in 발리", |
|
"desc": "발리 자연 속에서 명상·아사나 요가를 심도 있게 체험 (운동, 요가)", |
|
"personality": "내향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 135, |
|
"title": "리포머 필라테스 스튜디오", |
|
"desc": "기구 필라테스로 자세 교정과 코어 강화에 집중 (운동, 필라테스)", |
|
"personality": "내향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 136, |
|
"title": "아쿠아 스위밍 프로젝트", |
|
"desc": "실내 수영장에서 영법·호흡법을 익히고 오픈워터 도전 (운동, 수영)", |
|
"personality": "외향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 137, |
|
"title": "윔블던 테니스 레슨", |
|
"desc": "프로 코치와 함께 랠리·발리를 집중 훈련, 미니 매치 진행 (운동, 테니스)", |
|
"personality": "외향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 138, |
|
"title": "스크린 골프 라운딩", |
|
"desc": "필드 대신 스크린 골프로 스윙 교정하고 퍼팅 실력을 키움 (운동, 골프)", |
|
"personality": "내향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 139, |
|
"title": "실내 클라이밍 챌린지", |
|
"desc": "암벽등반 센터에서 다양한 코스를 공략하며 근지구력 향상 (운동, 클라이밍)", |
|
"personality": "외향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 140, |
|
"title": "풋볼 매치데이 이벤트", |
|
"desc": "축구 팬들이 모여 포메이션 짜고 미니 경기를 즐기는 일정 (운동, 축구)", |
|
"personality": "외향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 141, |
|
"title": "NBA 농구 액션타임", |
|
"desc": "코트에서 드리블·슛을 연습하고 3:3 스크리미지를 진행 (운동, 농구)", |
|
"personality": "외향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 142, |
|
"title": "락볼링 스트라이크 파티", |
|
"desc": "볼링장에서 음악과 함께 스트라이크 확률을 높이는 교류전 (운동, 볼링)", |
|
"personality": "내향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 143, |
|
"title": "셔틀콕 배드민턴 교실", |
|
"desc": "배드민턴 스매시·푸트워크 집중 연습 후 미니 토너먼트 (운동, 배드민턴)", |
|
"personality": "외향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 144, |
|
"title": "하프마라톤 러닝 레이스", |
|
"desc": "도심 10km·21km 레이스에 도전하며 기록 향상을 목표 (운동, 러닝)", |
|
"personality": "내향형, 감정형" |
|
}, |
|
|
|
|
|
{ |
|
"item_id": 145, |
|
"title": "강원도 로드트립", |
|
"desc": "차로 강원도 해안·산골 명소를 돌아보며 자연을 만끽 (여행, 국내여행)", |
|
"personality": "내향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 146, |
|
"title": "유럽 배낭 자유여행", |
|
"desc": "파리·로마·바르셀로나를 포함해 유럽 도시들을 여행 (여행, 해외여행)", |
|
"personality": "외향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 147, |
|
"title": "지리산 백패킹 트레일", |
|
"desc": "배낭 하나로 지리산 둘레길을 걸으며 자연 속 야영 (여행, 백패킹)", |
|
"personality": "내향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 148, |
|
"title": "캠핑 파이어나이트", |
|
"desc": "텐트 치고 모닥불을 즐기며 가족·친구와 소통하는 프로그램 (여행, 캠핑)", |
|
"personality": "외향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 149, |
|
"title": "서울 골목투어", |
|
"desc": "익선동·을지로 등 오래된 골목길 카페·맛집을 탐방 (여행, 도시여행)", |
|
"personality": "외향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 150, |
|
"title": "전주 한옥마을 미식 탐방", |
|
"desc": "전주의 비빔밥·막걸리·한정식을 둘러보는 식도락 여행 (여행, 맛집탐방)", |
|
"personality": "외향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 151, |
|
"title": "경복궁 문화탐방", |
|
"desc": "왕궁·전통 공연·박물관을 돌며 한국 역사를 배우는 코스 (여행, 문화탐방)", |
|
"personality": "내향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 152, |
|
"title": "제주 오름 힐링투어", |
|
"desc": "제주 오름·용암동굴을 걸으며 자연 속에서 재충전 (여행, 힐링여행)", |
|
"personality": "내향형, 감정형" |
|
}, |
|
|
|
|
|
{ |
|
"item_id": 153, |
|
"title": "무라카미 '1Q84' 북클럽", |
|
"desc": "하루키 장편소설을 함께 읽고 상징·메타포를 토론 (독서, 소설)", |
|
"personality": "내향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 154, |
|
"title": "윤동주 시 낭독회", |
|
"desc": "‘하늘과 바람과 별과 시’ 등 윤동주 시를 낭송하며 문학적 감성을 공유 (독서, 시)", |
|
"personality": "내향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 155, |
|
"title": "김영하 '여행의 이유' 에세이 살롱", |
|
"desc": "여행과 삶의 관계를 에세이로 탐구하고 느낀 점을 나누는 시간 (독서, 에세이)", |
|
"personality": "내향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 156, |
|
"title": "스티븐 코비 '성공하는 사람들의 7가지 습관'", |
|
"desc": "자기계발서를 함께 읽고 실천 전략을 세우는 성장 프로젝트 (독서, 자기계발)", |
|
"personality": "내향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 157, |
|
"title": "인문학 토크: '총, 균, 쇠' & '사피엔스'", |
|
"desc": "문명의 발전 과정을 다룬 명저를 비교·분석하는 심층 독서 (독서, 인문)", |
|
"personality": "내향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 158, |
|
"title": "유발 하라리 '사피엔스' 역사 세션", |
|
"desc": "인류 역사를 조망하는 '사피엔스'를 읽고 현재 사회와 연결 (독서, 역사)", |
|
"personality": "내향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 159, |
|
"title": "칼 세이건 '코스모스' 과학 독서모임", |
|
"desc": "우주와 과학적 사고를 확장해보는 '코스모스' 함께 읽기 (독서, 과학)", |
|
"personality": "내향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 160, |
|
"title": "애덤 스미스 '국부론' 경제포럼", |
|
"desc": "고전 경제학을 토대로 시장경제 원리를 이해하는 세미나 (독서, 경제/경영)", |
|
"personality": "내향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 161, |
|
"title": "니체 '차라투스트라는 이렇게 말했다' 철학 토론", |
|
"desc": "니체 철학을 깊이 있게 탐독하고 현대적 의미를 해석 (독서, 철학)", |
|
"personality": "내향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 162, |
|
"title": "빈센트 반 고흐 예술책 리뷰", |
|
"desc": "화가 반 고흐의 작품 세계를 미술서적으로 접하고 교감 (독서, 예술)", |
|
"personality": "내향형, 감정형" |
|
}, |
|
|
|
|
|
{ |
|
"item_id": 163, |
|
"title": "'러브 액츄얼리' 로맨스 상영회", |
|
"desc": "여러 커플의 사랑 이야기를 담은 '러브 액츄얼리'를 감상 후 토론 (영화, 로맨스)", |
|
"personality": "외향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 164, |
|
"title": "코미디 '미스터 빈' 특별전", |
|
"desc": "로완 앳킨슨의 바디 코미디를 감상하며 웃음 포인트를 분석 (영화, 코미디)", |
|
"personality": "외향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 165, |
|
"title": "'매드 맥스: 분노의 도로' 액션 토크", |
|
"desc": "포스트 아포칼립스 배경의 질주 액션을 해부하는 상영회 (영화, 액션)", |
|
"personality": "외향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 166, |
|
"title": "스릴러 '겟 아웃' 심리분석", |
|
"desc": "조던 필의 '겟 아웃'을 통해 공포와 사회적 메시지를 살피는 이벤트 (영화, 스릴러)", |
|
"personality": "외향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 167, |
|
"title": "공포 '컨저링' 오싹 체험", |
|
"desc": "심령현상을 다룬 공포영화 '컨저링'으로 사운드·분위기 연출을 탐구 (영화, 공포)", |
|
"personality": "외향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 168, |
|
"title": "'인터스텔라' SF 디스커션", |
|
"desc": "크리스토퍼 놀란의 우주·중력 이론을 담은 영화를 보고 과학적 설정 토론 (영화, SF)", |
|
"personality": "외향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 169, |
|
"title": "'반지의 제왕' 판타지 데이", |
|
"desc": "톨킨 세계관을 기반으로 한 대서사시를 연속 상영·분석 (영화, 판타지)", |
|
"personality": "외향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 170, |
|
"title": "'쇼생크 탈출' 드라마 감동", |
|
"desc": "인생영화 '쇼생크 탈출'을 보고 희망·인내의 메시지를 토론 (영화, 드라마)", |
|
"personality": "외향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 171, |
|
"title": "지브리 애니메이션 스페셜", |
|
"desc": "'센과 치히로' 등 지브리 작품을 상영하며 판타지·동심을 느끼는 감상회 (영화, 애니메이션)", |
|
"personality": "외향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 172, |
|
"title": "다큐 '지구(Earth)' 환경 포럼", |
|
"desc": "BBC 제작 다큐멘터리 '지구'를 보고 자연·환경 이슈를 논의 (영화, 다큐멘터리)", |
|
"personality": "외향형, 이성형" |
|
}, |
|
|
|
|
|
{ |
|
"item_id": 173, |
|
"title": "'엘든 링' RPG 도전", |
|
"desc": "프롬소프트웨어의 ‘엘든 링’에서 난이도 극복 전략과 빌드 연구 (게임, RPG)", |
|
"personality": "외향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 174, |
|
"title": "'오버워치2' FPS 팀플", |
|
"desc": "‘오버워치2’에서 역할 분담·전술 플레이를 협력하는 대전 (게임, FPS)", |
|
"personality": "외향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 175, |
|
"title": "'데빌 메이 크라이' 액션 콤보 연구", |
|
"desc": "콤보 액션의 정수 ‘데메크’ 시리즈를 플레이하며 기술을 익힘 (게임, 액션)", |
|
"personality": "외향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 176, |
|
"title": "'스타크래프트' 전략 빌드토론", |
|
"desc": "테란·저그·프로토스 종족별 빌드 오더·리플레이 분석 (게임, 전략)", |
|
"personality": "내향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 177, |
|
"title": "'심시티' 시뮬레이션 건설", |
|
"desc": "도시건설 게임 ‘심시티’로 자신만의 도시를 디자인하고 운영 (게임, 시뮬레이션)", |
|
"personality": "내향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 178, |
|
"title": "'피파23' 스포츠 대전", |
|
"desc": "축구게임 ‘피파23’에서 오프라인 리그전을 열고 순위를 집계 (게임, 스포츠)", |
|
"personality": "외향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 179, |
|
"title": "'테트리스99' 퍼즐 배틀로얄", |
|
"desc": "닌텐도 스위치 ‘테트리스99’로 다수 인원이 동시에 경쟁 (게임, 퍼즐)", |
|
"personality": "외향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 180, |
|
"title": "'DJMAX 리스펙트 V' 리듬 페스티벌", |
|
"desc": "국산 리듬게임 DJMAX로 콤보와 노트 패턴에 도전 (게임, 음악/리듬)", |
|
"personality": "외향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 181, |
|
"title": "'하스스톤' 카드 덱실험실", |
|
"desc": "블리자드 TCG ‘하스스톤’에서 전략적 덱 구성과 토너먼트 플레이 (게임, 카드)", |
|
"personality": "외향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 182, |
|
"title": "'로스트아크' MMORPG 레이드", |
|
"desc": "대규모 온라인 RPG ‘로스트아크’에서 길드원과 협동해 레이드를 공략 (게임, MMORPG)", |
|
"personality": "외향형, 이성형" |
|
}, |
|
|
|
|
|
{ |
|
"item_id": 183, |
|
"title": "니트 코바늘 인형 만들기", |
|
"desc": "뜨개질 기법으로 귀여운 인형이나 소품을 제작하는 워크숍 (공예, 뜨개질)", |
|
"personality": "내향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 184, |
|
"title": "프랑스자수 브로치 교실", |
|
"desc": "세심한 자수로 브로치·미니 파우치를 장식하는 공예 클래스 (공예, 자수)", |
|
"personality": "내향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 185, |
|
"title": "도예 핸드빌딩 체험", |
|
"desc": "흙을 손으로 빚어 머그·접시를 만드는 도자기 공예 (공예, 도자기)", |
|
"personality": "내향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 186, |
|
"title": "가죽공예 카드지갑 제작", |
|
"desc": "가죽 재단·바느질·엣지 마감 실습으로 카드지갑을 완성 (공예, 가죽공예)", |
|
"personality": "내향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 187, |
|
"title": "나무도마 목공 교실", |
|
"desc": "원목을 가공·샌딩하여 주방용 나무도마를 제작하는 기초 체험 (공예, 목공예)", |
|
"personality": "내향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 188, |
|
"title": "비즈공예 액세서리 디자인", |
|
"desc": "비즈 구슬로 팔찌·목걸이를 만들고 색채 감각을 익히는 클래스 (공예, 비즈공예)", |
|
"personality": "내향형, 감정형" |
|
}, |
|
{ |
|
"item_id": 189, |
|
"title": "소이 캔들 & 디퓨저 만들기", |
|
"desc": "천연 소이왁스로 향초, 디퓨저를 제작해 감성 분위기를 조성 (공예, 캔들/디퓨저)", |
|
"personality": "내향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 190, |
|
"title": "페이퍼크래프트 3D 미니어처", |
|
"desc": "종이 공예로 입체 구조물을 제작하고 채색·디테일을 살리는 교실 (공예, 페이퍼크래프트)", |
|
"personality": "내향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 191, |
|
"title": "마크라메 인테리어 월행잉", |
|
"desc": "매듭 기법을 응용해 벽걸이·화분홀더 등 인테리어 소품을 만드는 실습 (공예, 마크라메)", |
|
"personality": "내향형, 이성형" |
|
}, |
|
{ |
|
"item_id": 192, |
|
"title": "레진아트 키링 & 액세서리", |
|
"desc": "투명 레진에 색소·글리터를 활용해 키링·반지를 만드는 창작 (공예, 레진아트)", |
|
"personality": "내향형, 이성형" |
|
} |
|
|
|
] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def make_item_embedding_dict(items, model): |
|
item_embedding_dict = {} |
|
for item in items: |
|
text = f"{item['title']} {item['desc']}" |
|
emb = model.encode(text, convert_to_numpy=True) |
|
item_embedding_dict[item['item_id']] = emb |
|
return item_embedding_dict |
|
|
|
item_embedding_dict = make_item_embedding_dict(items, model_bert) |
|
|
|
|
|
|
|
|
|
|
|
def make_user_profile_text(user_profile: dict) -> str: |
|
ext = user_profile.get("extroversion", "") |
|
ft = user_profile.get("feeling_thinking", "") |
|
hobby = user_profile.get("hobby", "") |
|
detail = user_profile.get("detail_hobby", "") |
|
|
|
if hobby == "여행": |
|
return (f"저는 {ext}이며 {ft}한 성향을 가진 사람입니다. " |
|
f"여행을 정말 좋아하며, 특히 {detail}을 즐깁니다. " |
|
"다양한 문화와 맛집을 직접 체험하고 싶어합니다.") |
|
elif hobby == "운동": |
|
return (f"저는 {ext}이며 {ft}한 성향을 가진 사람입니다. " |
|
f"운동에 열정을 가지고 있으며, 특히 {detail}을 즐깁니다. " |
|
"건강과 체력을 증진시키고자 꾸준히 활동합니다.") |
|
elif hobby == "독서": |
|
return (f"저는 {ext}이며 {ft}한 성향을 가진 사람입니다. " |
|
f"독서를 사랑하며, 특히 {detail}에 깊은 관심을 가지고 있습니다. " |
|
"새로운 지식과 다양한 관점을 배우고자 합니다.") |
|
elif hobby == "영화": |
|
return (f"저는 {ext}이며 {ft}한 성향을 가진 사람입니다. " |
|
f"영화를 감상하는 것을 좋아하며, 특히 {detail} 장르에 큰 흥미를 느낍니다. " |
|
"감동과 재미를 동시에 경험하고 싶어합니다.") |
|
elif hobby == "게임": |
|
return (f"저는 {ext}이며 {ft}한 성향을 가진 사람입니다. " |
|
f"게임을 즐기며, 특히 {detail} 게임에 많은 관심을 가지고 있습니다. " |
|
"전략적 사고와 도전을 통해 성취감을 느끼고자 합니다.") |
|
elif hobby == "공예": |
|
return (f"저는 {ext}이며 {ft}한 성향을 가진 사람입니다. " |
|
f"공예 활동에 열정을 가지고 있으며, 특히 {detail}을 즐깁니다. " |
|
"창의적인 아이디어를 실현하며 섬세한 작업을 즐깁니다.") |
|
else: |
|
return f"저는 {ext}이며 {ft}한 성향을 가진 사람입니다. 다양한 취미를 즐기고 있습니다." |
|
|
|
|
|
|
|
|
|
|
|
def make_item_embedding_dict(items, model): |
|
item_embedding_dict = {} |
|
for item in items: |
|
text = f"{item['title']} {item['desc']}" |
|
emb = model.encode(text, convert_to_numpy=True) |
|
item_embedding_dict[item['item_id']] = emb |
|
return item_embedding_dict |
|
|
|
item_embedding_dict = make_item_embedding_dict(items, model_bert) |
|
|
|
|
|
|
|
|
|
|
|
def make_user_profile_text(user_profile: dict) -> str: |
|
ext = user_profile.get("extroversion", "") |
|
ft = user_profile.get("feeling_thinking", "") |
|
|
|
|
|
hobby_list = user_profile.get("hobby", []) |
|
detail_list = user_profile.get("detail_hobby", []) |
|
|
|
|
|
|
|
hobby_str = ", ".join(hobby_list) if hobby_list else "" |
|
detail_str = ", ".join(detail_list) if detail_list else "" |
|
|
|
|
|
|
|
text = ( |
|
f"저는 {ext}이며 {ft}한 성향을 가진 사람입니다. " |
|
f"주요 취미로는 {hobby_str}를 즐기고, " |
|
f"특히 {detail_str} 분야에 관심이 많습니다." |
|
) |
|
return text |
|
|
|
|
|
|
|
|
|
|
|
def get_user_embedding(user_profile: dict): |
|
user_text = make_user_profile_text(user_profile) |
|
return model_bert.encode(user_text, convert_to_numpy=True) |
|
|
|
def extract_hobby(desc): |
|
""" 예: "PT 전문 코치... (운동, 헬스)" -> "운동, 헬스" """ |
|
match = re.search(r"\((.*?)\)", desc) |
|
return match.group(1) if match else "" |
|
|
|
def cosine_similarity(vec1, vec2): |
|
norm1 = np.linalg.norm(vec1) |
|
norm2 = np.linalg.norm(vec2) |
|
if norm1 == 0 or norm2 == 0: |
|
return 0.0 |
|
return float(np.dot(vec1, vec2) / (norm1 * norm2)) |
|
|
|
def recommend_content_based(user_profile: dict, top_n=5): |
|
user_emb = get_user_embedding(user_profile) |
|
scored = [] |
|
seen_items = set() |
|
|
|
|
|
user_hobbies = user_profile.get("hobby", []) or [] |
|
user_details = user_profile.get("detail_hobby", []) or [] |
|
user_extroversion = user_profile.get("extroversion", "") |
|
user_feeling_thinking = user_profile.get("feeling_thinking", "") |
|
|
|
for item in items: |
|
item_id = item["item_id"] |
|
if item_id in seen_items: |
|
continue |
|
|
|
item_emb = item_embedding_dict[item_id] |
|
sim = cosine_similarity(user_emb, item_emb) |
|
|
|
|
|
weight = 1.0 |
|
|
|
|
|
desc_hobby = extract_hobby(item["desc"]) |
|
for h in user_hobbies: |
|
if h in desc_hobby: |
|
weight *= 1.15 |
|
|
|
|
|
for dh in user_details: |
|
if dh in desc_hobby: |
|
weight *= 1.3 |
|
|
|
|
|
personality_match_count = sum( |
|
trait in item["personality"] |
|
for trait in [user_extroversion, user_feeling_thinking] |
|
) |
|
if personality_match_count == 1: |
|
weight *= 1.2 |
|
elif personality_match_count == 2: |
|
weight *= 1.3 |
|
|
|
final_score = sim * weight |
|
scored.append((item, final_score)) |
|
|
|
|
|
scored.sort(key=lambda x: x[1], reverse=True) |
|
|
|
|
|
selected_items = [] |
|
for (item, score) in scored: |
|
if len(selected_items) >= top_n: |
|
break |
|
if item["item_id"] not in seen_items: |
|
seen_items.add(item["item_id"]) |
|
selected_items.append((item, score)) |
|
|
|
return selected_items |
|
|
|
|
|
if len(balanced_recommendations) < top_n: |
|
for item, score in scored: |
|
if len(balanced_recommendations) < top_n: |
|
balanced_recommendations.append((item, score)) |
|
else: |
|
break |
|
|
|
return balanced_recommendations |
|
|
|
|
|
|
|
|
|
|
|
|
|
HF_API_KEY = os.environ.get("HF_API_KEY", "YOUR_HF_API_KEY") |
|
API_URL = "https://api-inference.huggingface.co/models/Chanjeans/tfchatbot_2" |
|
HEADERS = {"Authorization": f"Bearer {HF_API_KEY}"} |
|
|
|
def chat_response(user_input, mode="emotion", max_retries=5): |
|
if mode not in ["emotion", "rational"]: |
|
raise HTTPException(status_code=400, detail="mode는 'emotion' 또는 'rational'이어야 합니다.") |
|
|
|
prompt = f"<{mode}><usr>{user_input}</usr><sys>" |
|
payload = { |
|
"inputs": prompt, |
|
"parameters": { |
|
"max_new_tokens": 128, |
|
"temperature": 0.7, |
|
"top_p": 0.9, |
|
"top_k": 50, |
|
"repetition_penalty": 1.2, |
|
"do_sample": True |
|
}, |
|
"options": {"wait_for_model": True} |
|
} |
|
|
|
for attempt in range(max_retries): |
|
response = requests.post(API_URL, headers=HEADERS, json=payload) |
|
if response.status_code == 200: |
|
try: |
|
result = response.json() |
|
if isinstance(result, list) and "generated_text" in result[0]: |
|
generated_text = result[0]["generated_text"] |
|
return generated_text.replace(prompt, "").strip() |
|
else: |
|
return "응답 형식이 예상과 다릅니다." |
|
except Exception as e: |
|
return f"JSON 파싱 오류: {e}" |
|
|
|
elif response.status_code == 503: |
|
|
|
error_info = response.json() |
|
estimated_time = error_info.get("estimated_time", 15) |
|
time.sleep(min(estimated_time, 15)) |
|
else: |
|
return f"API Error: {response.status_code}, {response.text}" |
|
|
|
return "🚨 모델 로딩이 너무 오래 걸립니다. 잠시 후 다시 시도하세요." |
|
|
|
|
|
|
|
device = torch.device("cuda" if torch.cuda.is_available() else "cpu") |
|
|
|
tokenizer = BertTokenizer.from_pretrained("monologg/kobert") |
|
bert_model = BertForSequenceClassification.from_pretrained("monologg/kobert", num_labels=2) |
|
bert_model.load_state_dict(torch.load("emotion_bert_model.pth", map_location=device)) |
|
bert_model.to(device) |
|
bert_model.eval() |
|
|
|
xgb_model = joblib.load("xgboost_model.pkl") |
|
vectorizer = joblib.load("tfidf_vectorizer.pkl") |
|
|
|
def predict_depression(text: str): |
|
encoding = tokenizer(text, truncation=True, padding="max_length", max_length=64, return_tensors="pt") |
|
input_ids = encoding["input_ids"].to(device) |
|
attention_mask = encoding["attention_mask"].to(device) |
|
with torch.no_grad(): |
|
outputs = bert_model(input_ids, attention_mask=attention_mask) |
|
probabilities = torch.nn.functional.softmax(outputs.logits, dim=1) |
|
kobert_score = probabilities[0][1].item() |
|
text_vec = vectorizer.transform([text]) |
|
xgb_proba = xgb_model.predict_proba(text_vec)[0][1] |
|
kobert_score = max(0.35, min(kobert_score, 0.88)) |
|
xgb_proba = max(0.3, min(xgb_proba, 0.83)) |
|
combined_score = (kobert_score * 0.55) + (xgb_proba * 0.45) |
|
if combined_score > 0.72: |
|
label = "상담 권장" |
|
elif combined_score > 0.68: |
|
label = "관심 필요" |
|
else: |
|
label = "정상" |
|
return combined_score, label |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.get("/") |
|
def home(): |
|
return {"message": "안녕하세요! 추천 & 챗봇 FastAPI 서버입니다."} |
|
|
|
|
|
|
|
class UserProfile(BaseModel): |
|
extroversion: str |
|
feeling_thinking: str |
|
hobby: str |
|
detail_hobby: str |
|
|
|
@app.post("/recommend") |
|
def recommend_api(profile: UserProfile): |
|
top_items = recommend_content_based(profile.dict(), top_n=5) |
|
results = [] |
|
for (item, score) in top_items: |
|
results.append({ |
|
"item_id": item["item_id"], |
|
"title": item["title"], |
|
"desc": item["desc"], |
|
"personality": item["personality"], |
|
"score": round(score, 4) |
|
}) |
|
return {"recommendations": results} |
|
|
|
|
|
|
|
class ChatRequest(BaseModel): |
|
user_input: str |
|
mode: str |
|
|
|
|
|
|
|
|
|
class ChatOrRecommendRequest(BaseModel): |
|
user_input: str |
|
mode: str |
|
|
|
|
|
extroversion: Optional[str] = None |
|
feeling_thinking: Optional[str] = None |
|
hobby: Optional[List[str]] = None |
|
detail_hobby: Optional[List[str]] = None |
|
|
|
|
|
|
|
@app.post("/chat_or_recommend") |
|
def chat_or_recommend(req: ChatOrRecommendRequest): |
|
""" |
|
1) 만약 '추천' 키워드가 포함되어 있다면 → 추천만(return) |
|
2) 없다면 → 우울분석 + 챗봇 응답 |
|
2-1) 우울도가 '상담 권장'이면 → 상담안내 + 챗봇만 |
|
2-2) 우울도가 상담 권장이 아니면 → 상담안내(필요 시) + 챗봇 + (취미 정보 있으면) 추천 |
|
""" |
|
user_input = req.user_input |
|
mode = req.mode.lower() |
|
RECOMMEND_KEYWORDS = ["추천", "추천해줘", "취미 추천"] |
|
HI_KEYWORDS = ["안녕","안녕!","안녕~","안녕?"] |
|
is_hi_query = any(keyword in user_input for keyword in HI_KEYWORDS) |
|
|
|
|
|
if is_hi_query: |
|
return { |
|
"mode": "chat", |
|
"response": "안녕! 만나서 반가워. 오늘 너의 기분은 어땠어?" |
|
} |
|
is_recommend_query = any(keyword in user_input for keyword in RECOMMEND_KEYWORDS) |
|
|
|
|
|
if is_recommend_query: |
|
|
|
user_profile = { |
|
"extroversion": req.extroversion or "", |
|
"feeling_thinking": req.feeling_thinking or "", |
|
"hobby": req.hobby or [], |
|
"detail_hobby": req.detail_hobby or [], |
|
} |
|
|
|
top_items = recommend_content_based(user_profile, top_n=5) |
|
|
|
rec_results = [] |
|
recommendation_msg = "너를 위한 추천 목록이야" |
|
for i, (item, score) in enumerate(top_items, start=1): |
|
clean_desc = re.sub(r"\(.*?\)", "", item["desc"]).strip() |
|
rec_results.append({ |
|
"item_id": item["item_id"], |
|
"title": item["title"], |
|
"desc": item["desc"], |
|
"personality": item["personality"], |
|
"score": round(score, 4) |
|
}) |
|
recommendation_msg += f"{i}. **{item['title']}** - {clean_desc}\n" |
|
|
|
return { |
|
"mode": "recommend_only", |
|
"response": recommendation_msg, |
|
"recommendations": rec_results |
|
} |
|
|
|
|
|
else: |
|
|
|
depression_score, depression_label = predict_depression(user_input) |
|
|
|
if depression_label == "정상": |
|
chatbot_msg = chat_response(user_input, mode=mode) |
|
return { |
|
"mode": "chat", |
|
"response": chatbot_msg, |
|
"depression_label": depression_label |
|
} |
|
|
|
counseling_response = "" |
|
if depression_label == "상담 권장": |
|
counseling_response = ( |
|
"입력하신 메시지에서 심각한 우울 신호가 감지되었습니다. " |
|
"전문 상담을 받으실 것을 권장드립니다.\n\n" |
|
) |
|
|
|
|
|
if mode not in ["emotion", "rational"]: |
|
raise HTTPException( |
|
status_code=400, |
|
detail="mode는 'emotion' 또는 'rational'이어야 합니다." |
|
) |
|
chatbot_msg = chat_response(user_input, mode=mode) |
|
|
|
|
|
recommendation_msg = "" |
|
recommendations_list = [] |
|
|
|
|
|
if depression_label == "관심 필요" and req.hobby: |
|
user_profile = { |
|
"extroversion": req.extroversion or "", |
|
"feeling_thinking": req.feeling_thinking or "", |
|
"hobby": req.hobby, |
|
"detail_hobby": req.detail_hobby or [], |
|
} |
|
top_items = recommend_content_based(user_profile, top_n=5) |
|
|
|
recommendation_msg = "너를 위한 맞춤 추천 목록을 알려줄게! ☺️" |
|
for i, (item, score) in enumerate(top_items, start=1): |
|
clean_desc = re.sub(r"\(.*?\)", "", item["desc"]).strip() |
|
recommendations_list.append({ |
|
"item_id": item["item_id"], |
|
"title": item["title"], |
|
"desc": item["desc"], |
|
"personality": item["personality"], |
|
"score": round(score, 4) |
|
}) |
|
recommendation_msg += f"{i}. **{item['title']}** - {clean_desc}\n" |
|
recommendation_msg += "\n이 중에서 어떤 활동이 가장 마음에 들어? 🌟" |
|
|
|
|
|
final_response = counseling_response + chatbot_msg |
|
|
|
|
|
|
|
response_dict = { |
|
"mode": "chat+recommend" if recommendation_msg else "chat", |
|
"response": final_response, |
|
"depression_label": depression_label, |
|
"recommendation_msg": recommendation_msg if recommendation_msg else None, |
|
"recommendations": recommendations_list if recommendations_list else None |
|
} |
|
if recommendation_msg: |
|
response_dict["recommendations"] = recommendations_list |
|
|
|
return response_dict |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|