Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -25,29 +25,28 @@ import tempfile
|
|
25 |
import glob
|
26 |
import shutil
|
27 |
|
28 |
-
# ───
|
29 |
import pyarrow.parquet as pq
|
30 |
from sklearn.feature_extraction.text import TfidfVectorizer
|
31 |
from sklearn.metrics.pairwise import cosine_similarity
|
32 |
|
33 |
-
# ───
|
34 |
import httpx
|
35 |
from httpx import RemoteProtocolError
|
36 |
|
37 |
-
# ▸ backoff
|
38 |
try:
|
39 |
import backoff
|
40 |
except ImportError:
|
41 |
-
logging.warning("`backoff`
|
42 |
|
43 |
def _simple_backoff_on_exception(exceptions, *args, **kwargs):
|
44 |
"""
|
45 |
-
|
46 |
-
|
47 |
-
- exceptions :
|
48 |
-
- max_tries : kwargs
|
49 |
-
- base : kwargs
|
50 |
-
기타 인자는 무시합니다.
|
51 |
"""
|
52 |
max_tries = kwargs.get("max_tries", 3)
|
53 |
base = kwargs.get("base", 2)
|
@@ -64,7 +63,7 @@ except ImportError:
|
|
64 |
raise
|
65 |
sleep = base ** attempt
|
66 |
logging.info(
|
67 |
-
f"[retry {attempt}/{max_tries}] {fn.__name__} -> {e} … {sleep}s
|
68 |
)
|
69 |
time.sleep(sleep)
|
70 |
return wrapper
|
@@ -76,7 +75,7 @@ except ImportError:
|
|
76 |
backoff = _DummyBackoff()
|
77 |
|
78 |
|
79 |
-
#
|
80 |
|
81 |
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "")
|
82 |
BRAVE_KEY = os.getenv("SERPHOUSE_API_KEY", "") # Brave Search API
|
@@ -85,23 +84,23 @@ KAGGLE_KEY = os.getenv("KAGGLE_KEY", "")
|
|
85 |
KAGGLE_API_KEY = KAGGLE_KEY
|
86 |
|
87 |
if not (KAGGLE_USERNAME and KAGGLE_KEY):
|
88 |
-
raise RuntimeError("⚠️ KAGGLE_USERNAME
|
89 |
|
90 |
os.environ["KAGGLE_USERNAME"] = KAGGLE_USERNAME
|
91 |
os.environ["KAGGLE_KEY"] = KAGGLE_KEY
|
92 |
|
93 |
BRAVE_ENDPOINT = "https://api.search.brave.com/res/v1/web/search"
|
94 |
-
IMAGE_API_URL = "http://211.233.58.201:7896" #
|
95 |
-
MAX_TOKENS = 7999 #
|
96 |
|
97 |
-
#
|
98 |
logging.basicConfig(
|
99 |
level=logging.INFO,
|
100 |
format="%(asctime)s - %(levelname)s - %(message)s"
|
101 |
)
|
102 |
|
103 |
|
104 |
-
# ───────────────────────────────
|
105 |
@st.cache_resource
|
106 |
def load_military_dataset():
|
107 |
"""
|
@@ -121,7 +120,7 @@ def load_military_dataset():
|
|
121 |
MIL_DF = load_military_dataset()
|
122 |
|
123 |
def is_military_query(text: str) -> bool:
|
124 |
-
"""
|
125 |
kw = [
|
126 |
"군사", "전술", "전투", "전쟁", "작전", "무기", "병력",
|
127 |
"military", "tactic", "warfare", "battle", "operation"
|
@@ -130,8 +129,8 @@ def is_military_query(text: str) -> bool:
|
|
130 |
|
131 |
def military_search(query: str, top_k: int = 3):
|
132 |
"""
|
133 |
-
|
134 |
-
|
135 |
"""
|
136 |
if MIL_DF is None:
|
137 |
return []
|
@@ -149,7 +148,8 @@ def military_search(query: str, top_k: int = 3):
|
|
149 |
logging.error(f"military_search error: {e}")
|
150 |
return []
|
151 |
|
152 |
-
|
|
|
153 |
KAGGLE_DATASETS = {
|
154 |
"general_business": {
|
155 |
"ref": "mohammadgharaei77/largest-2000-global-companies",
|
@@ -417,6 +417,7 @@ KAGGLE_DATASETS = {
|
|
417 |
}
|
418 |
}
|
419 |
|
|
|
420 |
SUN_TZU_STRATEGIES = [
|
421 |
{"계": "만천과해", "요약": "평범한 척, 몰래 진행", "조건": "상대가 지켜보고 있을 때", "행동": "루틴·평온함 과시", "목적": "경계 무력화", "예시": "규제기관 눈치 보는 신사업 파일럿"},
|
422 |
{"계": "위위구조", "요약": "뒤통수 치면 포위 풀린다", "조건": "우리 측이 압박받을 때", "행동": "적 본진 급습", "목적": "압박 해소", "예시": "경쟁사 핵심 고객 뺏기"},
|
@@ -458,7 +459,7 @@ SUN_TZU_STRATEGIES = [
|
|
458 |
|
459 |
physical_transformation_categories = {
|
460 |
"센서 기능": [
|
461 |
-
#
|
462 |
"시각 센서", "시각 감지", "청각 센서", "청각 감지", "촉각 센서", "촉각 감지",
|
463 |
"미각 센서", "미각 감지", "후각 센서", "후각 감지", "온도 센서", "온도 감지",
|
464 |
"습도 센서", "습도 감지", "압력 센서", "압력 감지", "가속도 센서", "가속도 감지",
|
@@ -476,7 +477,7 @@ physical_transformation_categories = {
|
|
476 |
"홀 효과 감지", "초음파 센서", "초음파 감지", "레이더 센서", "레이더 감지",
|
477 |
"라이다 센서", "라이다 감지", "터치 센서", "터치 감지", "제스처 센서", "제스처 감지",
|
478 |
"심박 센서", "심박 감지", "혈압 센서", "혈압 감지", "LAN", "WIFI", "블루투스", "생체 인증",
|
479 |
-
#
|
480 |
"다중 스펙트럼 센서", "다중 스펙트럼 감지", "깊이 인식 센서", "깊이 인식 감지",
|
481 |
"퀀텀 센서", "퀀텀 감지", "웨어러블 센서", "웨어러블 감지", "바이오마커 센서", "바이오마커 감지",
|
482 |
"임베디드 센서", "임베디드 감지", "IoT 센서 네트워크", "스트레인 센서", "스트레인 감지",
|
@@ -484,7 +485,7 @@ physical_transformation_categories = {
|
|
484 |
"스마트 먼지 센서", "환경 센서 그리드", "신경형태학적 센서", "두뇌-기계 인터페이스"
|
485 |
],
|
486 |
"크기와 형태 변화": [
|
487 |
-
#
|
488 |
"부피 늘어남", "부피 줄어듦", "길이 늘어남", "길이 줄어듦", "너비 늘어남", "너비 줄어남",
|
489 |
"높이 늘어남", "높이 줄어듦", "밀도 변화", "무게 증가", "무게 감소", "모양 변형",
|
490 |
"상태 변화", "불균등 변형", "복잡한 형태 변형", "비틀림", "꼬임", "불균일한 확장",
|
@@ -492,14 +493,14 @@ physical_transformation_categories = {
|
|
492 |
"물 저항", "먼지 저항", "찌그러짐", "복원", "접힘", "펼쳐짐", "압착", "팽창",
|
493 |
"늘어남", "수축", "구겨짐", "평평해짐", "뭉개짐", "단단해짐", "말림", "펴짐",
|
494 |
"꺾임", "구부러짐",
|
495 |
-
#
|
496 |
"4D 프린팅 변형", "형상 기억", "프랙탈 변화", "자가 조립", "자가 복구",
|
497 |
"기하학적 변환", "모듈화", "스마트 직물 변형", "매트릭스 구조 변형", "프로그래머블 변형",
|
498 |
"미시 스케일 변형", "거시 스케일 변형", "이방성 변형", "등방성 변형", "선택적 강성 변화",
|
499 |
"변형률 감응 구조", "형태학적 계산", "위상 변화", "경도 변화", "부드러움 변화"
|
500 |
],
|
501 |
"표면 및 외관 변화": [
|
502 |
-
#
|
503 |
"색상 변화", "질감 변화", "투명 변화", "불투명 변화", "반짝임 변화", "무광 변화",
|
504 |
"빛 반사 정도 변화", "무늬 변화", "각도에 따른 색상 변화", "빛에 따른 색상 변화",
|
505 |
"온도에 따른 색상 변화", "홀로그램 효과", "표면 각도별 빛 반사", "표면 모양 변형",
|
@@ -507,15 +508,15 @@ physical_transformation_categories = {
|
|
507 |
"선명함 변화", "광택 변화", "윤기 변화", "색조 변화", "채도 변화", "발광",
|
508 |
"형광", "빛 산란 효과", "빛 흡수 변화", "반투명 효과", "그림자 효과 변화",
|
509 |
"자외선 반응 변화", "야광 효과",
|
510 |
-
#
|
511 |
"생체모방 표면", "프로그래머블 질감", "촉각 피드백 표면", "열 반응성 표면",
|
512 |
"초소수성/초친수성 표면", "스마트 코팅", "마찰 계수 변화", "도금 효과", "위장 효과",
|
513 |
"양자점 효과", "메타표면 효과", "나노 구조화 표면", "전기변색 효과", "광변색 효과",
|
514 |
-
"압력변색 효과", "자기변색 효과", "항균 표면", "공기역학적 표면", "자기정렬 패턴",
|
515 |
"부착성 변화", "선택적 접착성"
|
516 |
],
|
517 |
"물질의 상태 변화": [
|
518 |
-
#
|
519 |
"고체 전환", "액체 전환", "기체 전환", "결정화", "용해", "산화", "부식",
|
520 |
"딱딱해짐", "부드러워짐", "특수 상태 전환", "무정형 전환", "결정형 전환", "성분 분리",
|
521 |
"미세 입자 형성", "미세 입자 분해", "젤 형성", "젤 풀어짐", "준안정 상태 변화",
|
@@ -523,7 +524,7 @@ physical_transformation_categories = {
|
|
523 |
"증발", "응축", "승화", "증착", "침전", "부유", "분산", "응집",
|
524 |
"건조", "습윤", "팽윤", "수축", "동결", "해동", "풍화", "침식",
|
525 |
"충전", "방전", "결합", "분리", "발효", "부패",
|
526 |
-
#
|
527 |
"초임계 상태 전환", "양자 상태 전환", "메타물질 상태 변화", "프로그래머블 물질 변화",
|
528 |
"소프트 로봇 물질 변화", "4D 프린팅 물질 변화", "바이오하이브리드 물질 변화",
|
529 |
"자가 조직화 물질", "자가 순환 물질", "자가 치유 물질", "생분해성 전환",
|
@@ -531,14 +532,14 @@ physical_transformation_categories = {
|
|
531 |
"스마트 유체 상태", "자극 반응성 상태", "형상기억 합금 상태", "초전도 상태"
|
532 |
],
|
533 |
"움직임 특성 변화": [
|
534 |
-
#
|
535 |
"가속", "감속", "일정 속도 유지", "진동", "진동 감소", "부딪힘", "튕김",
|
536 |
"회전 속도 증가", "회전 속도 감소", "회전 방향 변화", "불규칙 움직임", "멈췄다", "미끄러지는 현상",
|
537 |
"공진", "반공진", "유체 속 저항 변화", "유체 속 양력 변화", "움직임 저항 변화",
|
538 |
"복합 진동 움직임", "특수 유체 속 움직임", "회전-이동 연계 움직임", "관성 정지",
|
539 |
"충격 흡수", "충격 전달", "운동량 보존", "마찰력 변화", "관성 탈출", "불안정 균형",
|
540 |
"동적 안정성", "흔들림 감쇠", "경로 예측성", "회피 움직임",
|
541 |
-
#
|
542 |
"복합 운동학", "임의의 움직임", "재귀적 움직임", "흉내내는 움직임", "학습된 움직임",
|
543 |
"자율적 움직임", "군집 움직임", "비선형 움직임", "양자 움직임", "초음파 움직임",
|
544 |
"비대칭 움직임", "스토캐스틱 움직임", "카오스 움직임", "소프트 로보틱스 움직임",
|
@@ -546,7 +547,7 @@ physical_transformation_categories = {
|
|
546 |
"계층적 움직임 제어", "적응형 움직임 패턴"
|
547 |
],
|
548 |
"구조적 변화": [
|
549 |
-
#
|
550 |
"부품 추가", "부품 제거", "조립", "분해", "접기", "펴기", "변형", "원상복구",
|
551 |
"최적 구조 변화", "자가 재배열", "자연 패턴 형성", "자연 패턴 소멸", "규칙적 패턴 변화",
|
552 |
"모듈식 변형", "복잡성 증가 구조", "원래 모양 기억 효과", "시간에 따른 형태 변화",
|
@@ -554,20 +555,20 @@ physical_transformation_categories = {
|
|
554 |
"내부 구조 변화", "외부 구조 변화", "중심축 이동", "균형점 변화", "계층 구조 변화",
|
555 |
"지지 구조 변화", "응력 분산 구조", "충격 흡수 구조", "그리드 구조 변화", "매트릭스 구조 변화",
|
556 |
"상호 연결성 변화",
|
557 |
-
#
|
558 |
"텐세그리티 구조 변화", "바이오닉 구조", "메타물질 구조", "다중 안정 구조", "자가 진화 구조",
|
559 |
"자가 학습 구조", "생체모방 구조", "프랙탈 구조", "계층적 구조화", "에너지 흡수 구조",
|
560 |
"에너지 변환 구조", "적응형 구조", "위상 최적화", "다공성 구조", "기능적 경사 구조",
|
561 |
"다중 재료 구조", "초경량 구조", "초고강도 구조", "다기능성 구조", "내결함성 구조"
|
562 |
],
|
563 |
"공간 이동": [
|
564 |
-
#
|
565 |
"앞 이동", "뒤 이동", "좌 이동", "우 이동", "위 이동", "아래 이동",
|
566 |
"세로축 회전(고개 끄덕임)", "가로축 회전(고개 젓기)", "길이축 회전(옆으로 기울임)", "원 운동",
|
567 |
"나선형 이동", "관성에 의한 미끄러짐", "회전축 변화", "불규칙 회전", "흔들림 운동",
|
568 |
"포물선 이동", "무중력 부유", "수면 위 부유", "점프", "도약", "슬라이딩", "롤링",
|
569 |
"자유 낙하", "왕복 운동", "탄성 튕김", "관통", "회피 움직임", "지그재그 이동", "스윙 운동",
|
570 |
-
#
|
571 |
"양자 이동", "차원 간 이동", "가상 공간 이동", "증강 공간 이동", "무인 이동",
|
572 |
"군집 이동", "경로 최적화 이동", "상황 인식 이동", "생태계 통합 이동", "바이오닉 이동",
|
573 |
"미시 스케일 이동", "매크로 스케일 이동", "변형 기반 이동", "자기장 유도 이동",
|
@@ -575,14 +576,14 @@ physical_transformation_categories = {
|
|
575 |
"지능형 경로 탐색"
|
576 |
],
|
577 |
"시간 관련 변화": [
|
578 |
-
#
|
579 |
"노화", "풍화", "마모", "부식", "색 바램", "변색", "손상", "회복",
|
580 |
"수명 주기 변화", "사용자 상호작용에 따른 적응", "학습 기반 형태 최적화", "시간에 따른 물성 변화",
|
581 |
"집단 기억 효과", "문화적 의미 변화", "지연 반응", "이전 상태 의존 변화", "점진적 시간 변화",
|
582 |
"진화적 변화", "주기적 재생", "계절 변화 적응", "생체리듬 변화", "생애 주기 단계",
|
583 |
"성장", "퇴화", "자가 복구", "자가 재생", "자연 순환 적응", "지속성", "일시성",
|
584 |
"기억 효과", "지연된 작용", "누적 효과",
|
585 |
-
#
|
586 |
"시간 지연 효과", "예측 기반 변화", "학습 기반 변화", "인지적 시간 변화",
|
587 |
"시간 압축 경험", "시간 확장 경험", "시간적 패턴 감지", "시간적 패턴 생성",
|
588 |
"계절 인식 변화", "생체 시계 동기화", "시간 기반 프로그래밍", "연대기적 데이터 구조",
|
@@ -590,26 +591,26 @@ physical_transformation_categories = {
|
|
590 |
"사용 패턴 적응", "사용자 타임라인 통합", "예측 유지보수", "자가 최적화 타이밍"
|
591 |
],
|
592 |
"빛과 시각 효과": [
|
593 |
-
#
|
594 |
"발광", "소등", "빛 투과", "빛 차단", "빛 산란", "빛 집중", "색상 스펙트럼 변화",
|
595 |
"빛 회절", "빛 간섭", "홀로그램 생성", "레이저 효과", "빛 편광", "형광", "인광",
|
596 |
"자외선 발광", "적외선 발광", "광학적 착시", "빛 굴절", "그림자 생성", "그림자 제거",
|
597 |
"색수차 효과", "무지개 효과", "글로우 효과", "플래시 효과", "조명 패턴", "빔 효과",
|
598 |
"광 필터 효과", "빛의 방향성 변화", "투영 효과", "빛 감지", "빛 반응", "광도 변화",
|
599 |
-
#
|
600 |
"양자 발광", "메타 광학 효과", "프로그래머블 광학", "시야각 변화", "생체발광 모방",
|
601 |
"광역학 효과", "퍼셉션 변화 효과", "맥스웰리안 뷰", "광 컴퓨팅 효과", "광유전학 효과",
|
602 |
"생체 광학 모방", "비선형 광학 효과", "구조색 변화", "자가 발광", "광 결정 효과",
|
603 |
"양자점 방출", "나노 발광체", "증강 광학", "투명 디스플레이 효과", "광학 위장"
|
604 |
],
|
605 |
"소리와 진동 효과": [
|
606 |
-
#
|
607 |
"소리 발생", "소리 소멸", "음 높낮이 변화", "음량 변화", "음색 변화", "공명",
|
608 |
"반공명", "음향 진동", "초음파 발생", "저음파 발생", "소리 집중", "소리 분산",
|
609 |
"음향 반사", "음향 흡수", "음향 도플러 효과", "음파 간섭", "음향 공진", "진동 패턴 변화",
|
610 |
"타악 효과", "음향 피드백", "음향 차폐", "음향 증폭", "소리 지향성", "소리 왜곡",
|
611 |
"비트 생성", "배음 생성", "주파수 변조", "음향 충격파", "음향 필터링",
|
612 |
-
#
|
613 |
"메타 음향 효과", "방향성 음향", "3D 음향 효과", "생체음향 모방", "상황별 음향 변화",
|
614 |
"음향 위장", "음향 투명화", "음향 렌즈", "양자 음향 효과", "초저주파 효과",
|
615 |
"초고주파 효과", "음파 에너지 수확", "자가 조절 공명", "음향 홀로그래피",
|
@@ -617,7 +618,7 @@ physical_transformation_categories = {
|
|
617 |
"기능적 음향 표면", "구조 공진 조절"
|
618 |
],
|
619 |
"열 관련 변화": [
|
620 |
-
#
|
621 |
"온도 상승", "온도 하강", "열 팽창", "열 수축", "열 전달", "열 차단", "압력 상승",
|
622 |
"압력 하강", "열 변화에 따른 자화", "엔트로피 변화", "열전기 효과", "자기장에 의한 열 변화",
|
623 |
"상태 변화 중 열 저장", "상태 변화 중 열 방출", "열 스트레스 발생", "열 스트레스 해소",
|
@@ -625,14 +626,14 @@ physical_transformation_categories = {
|
|
625 |
"열 반사", "열 흡수", "냉각 응축", "열 활성화", "열 변색", "열 팽창 계수 변화",
|
626 |
"열 안정성 변화", "내열성", "내한성", "자가 발열", "열적 평형", "열적 불균형",
|
627 |
"열적 변형", "열 분산", "열 집중",
|
628 |
-
#
|
629 |
"열전 효과", "열광 효과", "프로그래머블 열 특성", "상변화 냉각", "상변화 가열",
|
630 |
"열 유도 기억", "열 광학 효과", "열 음향 효과", "열 기계 효과", "열 화학 반응",
|
631 |
"양자 열역학 효과", "근적외선 열 효과", "열 조절 표면", "열흐름 제어", "열 방출 최적화",
|
632 |
"열 캡처 최적화", "자가 조절 온도", "열 스위칭", "열 포커싱", "상변화 재료 활용"
|
633 |
],
|
634 |
"전기 및 자기 변화": [
|
635 |
-
#
|
636 |
"자성 생성", "자성 소멸", "전하량 증가", "전하량 감소", "전기장 생성", "전기장 소멸",
|
637 |
"자기장 생성", "자기장 소멸", "초전도 상태 전환", "강유전체 특성 변화", "양자 상태 변화",
|
638 |
"플라즈마 형성", "플라즈마 소멸", "스핀파 전달", "빛에 의한 전기 발생", "압력에 의한 전기 발생",
|
@@ -640,7 +641,7 @@ physical_transformation_categories = {
|
|
640 |
"전자기 유도", "전자기파 방출", "전자기파 흡수", "전기 용량 변화", "자기 이력 현상",
|
641 |
"전기적 분극", "전자 흐름 방향 변화", "전기적 공명", "전기적 차폐", "전기적 노출",
|
642 |
"자기 차폐", "자기 노출", "자기장 정렬", "유선(Wire)", "무선(Wireless)",
|
643 |
-
#
|
644 |
"양자 자성", "스핀트로닉스 효과", "마그네토일렉트릭 효과", "토폴로지컬 절연체 특성",
|
645 |
"초전도 양자 효과", "쿨롱 차단 효과", "조셉슨 효과", "홀 효과 변화", "전자기 투명성",
|
646 |
"자기 카이랄리티", "전자기 메타표면", "무선 전력 전송", "자기유변학적 효과",
|
@@ -648,14 +649,14 @@ physical_transformation_categories = {
|
|
648 |
"전자 스핀 제어", "고속 스위칭 자성"
|
649 |
],
|
650 |
"화학적 변화": [
|
651 |
-
#
|
652 |
"표면 코팅 변화", "물질 성분 변화", "화학 반응 변화", "촉매 작용 시작/중단",
|
653 |
"빛에 의한 화학 반응", "전기에 의한 화학 반응", "단분자막 형성", "분자 수준 구조 변화",
|
654 |
"생체 모방 표면 변화", "환경 반응형 물질 변화", "주기적 화학 반응", "산화", "환원",
|
655 |
"고분자화", "물 분해", "화합", "방사선 영향", "산-염기 반응", "중화 반응",
|
656 |
"이온화", "화학적 흡착/탈착", "촉매 효율 변화", "효소 활성 변화", "발색 반응",
|
657 |
"pH 변화", "화학적 평형 이동", "결합 형성/분해", "용해도 변화",
|
658 |
-
#
|
659 |
"프로그래머블 화학 반응", "자가 촉매 반응", "클릭 케미스트리", "광화학 반응",
|
660 |
"전기화학 반응", "초분자 화학 반응", "동적 공유 결합", "바이오오쏘고널 화학",
|
661 |
"화학적 컴퓨팅", "화학적 감지", "화학적 통신", "화학적 기억", "선택적 촉매",
|
@@ -663,13 +664,13 @@ physical_transformation_categories = {
|
|
663 |
"화학적 패턴 형성", "화학적 습도 조절", "화학적 정화"
|
664 |
],
|
665 |
"생물학적 변화": [
|
666 |
-
#
|
667 |
"성장/위축", "세포 분열/사멸", "생물 발광", "신진대사 변화", "면역 반응",
|
668 |
"호르몬 분비", "신경 반응", "유전적 발현", "적응/진화", "생체리듬 변화",
|
669 |
"재생/치유", "노화/성숙", "생체 모방 변화", "바이오필름 형성", "생물학적 분해",
|
670 |
"효소 활성화/비활성화", "생물학적 신호 전달", "스트레스 반응", "체온 조절", "생물학적 시계 변화",
|
671 |
"세포외 기질 변화", "생체 역학적 반응", "세포 운동성", "세포 극성 변화", "영양 상태 변화",
|
672 |
-
#
|
673 |
"합성 생물학 반응", "생물학적 컴퓨팅", "오가노이드 발달", "인공 조직 발달",
|
674 |
"생체적합성 변화", "면역학적 응답 제어", "후성유전학적 변화", "생물학적 리듬 조절",
|
675 |
"신경가소성 효과", "세포외 기질 리모델링", "체세포 리프로그래밍", "생체활성 표면 상호작용",
|
@@ -677,13 +678,13 @@ physical_transformation_categories = {
|
|
677 |
"바이오하이브리드 시스템", "세포 분화 조절", "생���신호 증폭", "생화학적 기억 형성"
|
678 |
],
|
679 |
"환경 상호작용": [
|
680 |
-
#
|
681 |
"온도 반응", "습도 반응", "기압 반응", "중력 반응", "자기장 반응",
|
682 |
"빛 반응", "소리 반응", "화학 물질 감지", "기계적 자극 감지", "전기 자극 반응",
|
683 |
"방사선 반응", "진동 감지", "pH 반응", "용매 반응", "기체 교환",
|
684 |
"환경 오염 반응", "날씨 반응", "계절 반응", "일주기 반응", "생태계 상호작용",
|
685 |
"공생/경쟁 반응", "포식/피식 관계", "군집 형성", "영역 설정", "이주 패턴", "정착 패턴",
|
686 |
-
#
|
687 |
"탄소 포집 및 변환", "생태계 복원 효과", "생물다양성 증진", "순환 경제 상호작용",
|
688 |
"도시 환경 통합", "스마트 환경 감지", "재생 가능 에너지 연계", "물 순환 상호작용",
|
689 |
"대기 질 상호작용", "자연 기반 솔루션 통합", "재해 복원력 증진", "기후 변화 적응",
|
@@ -691,7 +692,7 @@ physical_transformation_categories = {
|
|
691 |
"환경적 자가 수정", "지속가능한 자원 관리", "생태계 건강 모니터링", "생태 교란 방지"
|
692 |
],
|
693 |
"비즈니스 아이디어": [
|
694 |
-
#
|
695 |
"시장 재정의/신규 시장 개척",
|
696 |
"비즈니스 모델 혁신/디지털 전환",
|
697 |
"고객 경험 혁신/서비스 혁신",
|
@@ -702,7 +703,7 @@ physical_transformation_categories = {
|
|
702 |
"지속 가능한 성장/사회적 가치 창출",
|
703 |
"데이터 기반 의사결정/AI 도입",
|
704 |
"신기술 융합/혁신 투자",
|
705 |
-
#
|
706 |
"탄소중립 비즈니스 모델",
|
707 |
"순환경제 비즈니스 모델",
|
708 |
"구독 경제 모델",
|
@@ -720,7 +721,7 @@ physical_transformation_categories = {
|
|
720 |
"웰빙/웰니스 중심 비즈니스"
|
721 |
],
|
722 |
|
723 |
-
#
|
724 |
|
725 |
"사용자 인터페이스 및 상호작용": [
|
726 |
"제스처 인식", "제스처 제어", "음성 인식", "음성 제어", "시선 추적", "시선 제어",
|
@@ -797,91 +798,92 @@ physical_transformation_categories = {
|
|
797 |
]
|
798 |
}
|
799 |
|
800 |
-
#
|
801 |
SWOT_FRAMEWORK = {
|
802 |
"strengths": {
|
803 |
-
"title": "
|
804 |
-
"description": "
|
805 |
"prompt_keywords": ["강점", "장점", "우위", "역량", "자산", "전문성", "strength", "advantage"]
|
806 |
},
|
807 |
"weaknesses": {
|
808 |
-
"title": "
|
809 |
-
"description": "
|
810 |
"prompt_keywords": ["약점", "단점", "부족", "한계", "취약점", "weakness", "limitation", "deficit"]
|
811 |
},
|
812 |
"opportunities": {
|
813 |
-
"title": "
|
814 |
-
"description": "
|
815 |
"prompt_keywords": ["기회", "가능성", "트렌드", "변화", "성장", "opportunity", "trend", "potential"]
|
816 |
},
|
817 |
"threats": {
|
818 |
-
"title": "
|
819 |
-
"description": "
|
820 |
"prompt_keywords": ["위협", "리스크", "경쟁", "위험", "장벽", "threat", "risk", "competition", "barrier"]
|
821 |
}
|
822 |
}
|
823 |
|
824 |
PORTER_FRAMEWORK = {
|
825 |
"rivalry": {
|
826 |
-
"title": "
|
827 |
-
"description": "
|
828 |
"prompt_keywords": ["경쟁", "경쟁사", "시장점유율", "가격경쟁", "competition", "rival", "market share"]
|
829 |
},
|
830 |
"new_entrants": {
|
831 |
-
"title": "
|
832 |
-
"description": "
|
833 |
"prompt_keywords": ["진입장벽", "신규", "스타트업", "entry barrier", "newcomer", "startup"]
|
834 |
},
|
835 |
"substitutes": {
|
836 |
-
"title": "
|
837 |
-
"description": "
|
838 |
"prompt_keywords": ["대체재", "대안", "substitute", "alternative", "replacement"]
|
839 |
},
|
840 |
"buyer_power": {
|
841 |
-
"title": "
|
842 |
-
"description": "
|
843 |
"prompt_keywords": ["고객", "구매자", "가격민감도", "협상력", "customer", "buyer power"]
|
844 |
},
|
845 |
"supplier_power": {
|
846 |
-
"title": "
|
847 |
-
"description": "
|
848 |
"prompt_keywords": ["공급자", "벤더", "원재료", "supplier", "vendor", "raw material"]
|
849 |
}
|
850 |
}
|
851 |
|
852 |
BCG_FRAMEWORK = {
|
853 |
"stars": {
|
854 |
-
"title": "
|
855 |
-
"description": "
|
856 |
"prompt_keywords": ["성장", "점유율", "중점", "투자", "star", "growth", "investment"]
|
857 |
},
|
858 |
"cash_cows": {
|
859 |
-
"title": "
|
860 |
-
"description": "
|
861 |
"prompt_keywords": ["안정", "수익", "현금", "전통", "cash cow", "profit", "mature"]
|
862 |
},
|
863 |
"question_marks": {
|
864 |
-
"title": "
|
865 |
-
"description": "
|
866 |
"prompt_keywords": ["가능성", "위험", "불확실", "잠재", "question mark", "uncertain", "potential"]
|
867 |
},
|
868 |
"dogs": {
|
869 |
-
"title": "
|
870 |
-
"description": "
|
871 |
"prompt_keywords": ["회수", "철수", "저성장", "비효율", "dog", "divest", "low growth"]
|
872 |
}
|
873 |
}
|
874 |
|
875 |
BUSINESS_FRAMEWORKS = {
|
876 |
-
"sunzi": "
|
877 |
-
"swot": "SWOT
|
878 |
-
"porter": "Porter
|
879 |
-
"bcg": "BCG
|
880 |
}
|
881 |
|
|
|
882 |
@dataclass
|
883 |
class Category:
|
884 |
-
"""
|
885 |
name_ko: str
|
886 |
name_en: str
|
887 |
tags: list[str]
|
@@ -951,26 +953,26 @@ def format_business_framework_analysis(framework_type: str, analysis_result: dic
|
|
951 |
if not analysis_result:
|
952 |
return ""
|
953 |
titles = {
|
954 |
-
'swot': '# SWOT
|
955 |
-
'porter': '# Porter
|
956 |
-
'bcg': '# BCG
|
957 |
}
|
958 |
-
md = f"{titles.get(framework_type, '#
|
959 |
-
md += "
|
960 |
for category, info in analysis_result.items():
|
961 |
md += f"## {info['title']}\n\n"
|
962 |
md += f"{info['description']}\n\n"
|
963 |
-
md += f"
|
964 |
if info['keywords']:
|
965 |
-
md += "
|
966 |
for keyword in info['keywords']:
|
967 |
md += f"- *{keyword}*\n"
|
968 |
md += "\n"
|
969 |
else:
|
970 |
-
md += "
|
971 |
return md
|
972 |
|
973 |
-
#
|
974 |
def md_to_html(md_text: str, title: str = "Output") -> str:
|
975 |
html_content = markdown.markdown(
|
976 |
md_text,
|
@@ -1064,36 +1066,36 @@ def md_to_html(md_text: str, title: str = "Output") -> str:
|
|
1064 |
</html>
|
1065 |
"""
|
1066 |
|
1067 |
-
#
|
1068 |
def process_text_file(uploaded_file):
|
1069 |
try:
|
1070 |
content = uploaded_file.read().decode('utf-8')
|
1071 |
-
return f"""#
|
1072 |
|
1073 |
{content}
|
1074 |
"""
|
1075 |
except Exception as e:
|
1076 |
-
logging.error(f"
|
1077 |
return f"**Error processing {uploaded_file.name}**: {str(e)}"
|
1078 |
|
1079 |
def process_csv_file(uploaded_file):
|
1080 |
try:
|
1081 |
df = pd.read_csv(uploaded_file)
|
1082 |
-
return f"""#
|
1083 |
|
1084 |
-
##
|
1085 |
-
-
|
1086 |
-
-
|
1087 |
-
-
|
1088 |
|
1089 |
-
##
|
1090 |
{df.head(5).to_markdown(index=False)}
|
1091 |
|
1092 |
-
##
|
1093 |
{df.describe().to_markdown()}
|
1094 |
"""
|
1095 |
except Exception as e:
|
1096 |
-
logging.error(f"CSV
|
1097 |
return f"**Error processing {uploaded_file.name}**: {str(e)}"
|
1098 |
|
1099 |
def process_pdf_file(uploaded_file):
|
@@ -1108,16 +1110,16 @@ def process_pdf_file(uploaded_file):
|
|
1108 |
pages_preview.append(f"--- Page {page_num+1} ---\n{page.extract_text()}")
|
1109 |
|
1110 |
preview_text = "\n\n".join(pages_preview)
|
1111 |
-
return f"""#
|
1112 |
|
1113 |
-
##
|
1114 |
-
-
|
1115 |
|
1116 |
-
##
|
1117 |
{preview_text}
|
1118 |
"""
|
1119 |
except Exception as e:
|
1120 |
-
logging.error(f"PDF
|
1121 |
return f"**Error processing {uploaded_file.name}**: {str(e)}"
|
1122 |
|
1123 |
def process_uploaded_files(uploaded_files):
|
@@ -1142,12 +1144,13 @@ def process_uploaded_files(uploaded_files):
|
|
1142 |
f"# Unsupported file: {file.name}\n\nThis file type is not supported for processing."
|
1143 |
)
|
1144 |
except Exception as e:
|
1145 |
-
logging.error(f"
|
1146 |
file_contents.append(f"# Error processing file: {file.name}\n\n{str(e)}")
|
1147 |
|
1148 |
-
return "\n\n#
|
|
|
1149 |
|
1150 |
-
#
|
1151 |
def generate_image(prompt: str):
|
1152 |
if not prompt:
|
1153 |
return None, None
|
@@ -1180,11 +1183,11 @@ def generate_image(prompt: str):
|
|
1180 |
logging.error(f"Image generation error: {str(e)}", exc_info=True)
|
1181 |
return None, None
|
1182 |
|
1183 |
-
#
|
1184 |
@st.cache_resource
|
1185 |
def check_kaggle_availability():
|
1186 |
if not KAGGLE_API_KEY:
|
1187 |
-
logging.warning("Kaggle API
|
1188 |
return False
|
1189 |
return True
|
1190 |
|
@@ -1223,7 +1226,7 @@ def search_kaggle_datasets(query: str, top: int = 5) -> list[dict]:
|
|
1223 |
@st.cache_data
|
1224 |
def download_and_analyze_dataset(dataset_ref: str, max_rows: int = 1000):
|
1225 |
if not (os.getenv("KAGGLE_USERNAME") and os.getenv("KAGGLE_KEY")):
|
1226 |
-
return "Kaggle API
|
1227 |
api = KaggleApi()
|
1228 |
api.authenticate()
|
1229 |
tmpdir = tempfile.mkdtemp()
|
@@ -1232,12 +1235,12 @@ def download_and_analyze_dataset(dataset_ref: str, max_rows: int = 1000):
|
|
1232 |
except Exception as e:
|
1233 |
logging.error(f"Dataset download failed ({dataset_ref}): {e}")
|
1234 |
shutil.rmtree(tmpdir)
|
1235 |
-
return f"
|
1236 |
|
1237 |
csv_files = glob.glob(f"{tmpdir}/**/*.csv", recursive=True)
|
1238 |
if not csv_files:
|
1239 |
shutil.rmtree(tmpdir)
|
1240 |
-
return "CSV
|
1241 |
|
1242 |
try:
|
1243 |
df = pd.read_csv(csv_files[0], nrows=max_rows)
|
@@ -1249,30 +1252,30 @@ def download_and_analyze_dataset(dataset_ref: str, max_rows: int = 1000):
|
|
1249 |
"missing_values": df.isnull().sum().to_dict()
|
1250 |
}
|
1251 |
except Exception as e:
|
1252 |
-
analysis = f"CSV
|
1253 |
|
1254 |
shutil.rmtree(tmpdir)
|
1255 |
return analysis
|
1256 |
|
1257 |
def format_kaggle_analysis_markdown_multi(analyses: list[dict]) -> str:
|
1258 |
"""
|
1259 |
-
|
1260 |
analyses = [ {"meta": {...}, "analysis": {... or str}}, ... ]
|
1261 |
"""
|
1262 |
if not analyses:
|
1263 |
-
return "# Kaggle
|
1264 |
-
md = "# Kaggle
|
1265 |
-
md += "
|
1266 |
for i, item in enumerate(analyses, 1):
|
1267 |
ds = item["meta"]
|
1268 |
ana = item["analysis"]
|
1269 |
md += f"## {i}. {ds['title']}\n\n"
|
1270 |
md += f"{ds['subtitle']}\n\n"
|
1271 |
-
md += f"-
|
1272 |
-
md += f"- **URL
|
1273 |
if isinstance(ana, dict):
|
1274 |
-
md += f"
|
1275 |
-
md += "<details><summary
|
1276 |
try:
|
1277 |
md += pd.DataFrame(ana["head"]).to_markdown(index=False) + "\n\n"
|
1278 |
except:
|
@@ -1287,23 +1290,21 @@ def format_kaggle_analysis_markdown_multi(analyses: list[dict]) -> str:
|
|
1287 |
md += "---\n\n"
|
1288 |
return md
|
1289 |
|
1290 |
-
#
|
1291 |
@st.cache_resource
|
1292 |
def get_openai_client():
|
1293 |
if not OPENAI_API_KEY:
|
1294 |
-
raise RuntimeError("⚠️ OPENAI_API_KEY
|
1295 |
return OpenAI(
|
1296 |
api_key=OPENAI_API_KEY,
|
1297 |
timeout=60.0,
|
1298 |
max_retries=3
|
1299 |
)
|
1300 |
|
1301 |
-
#
|
1302 |
def identify_decision_purpose(prompt: str) -> dict:
|
1303 |
"""
|
1304 |
-
|
1305 |
-
(기존의 의사결정 목적/제약 식별 로직을 재활용하되,
|
1306 |
-
design/invention 관련 키워드도 추가로 고려할 수 있음.)
|
1307 |
"""
|
1308 |
purpose_patterns = {
|
1309 |
'cost_reduction': [r'비용(\s*절감)?', r'예산', r'효율', r'저렴', r'경제', r'cost', r'saving', r'budget'],
|
@@ -1337,7 +1338,7 @@ def identify_decision_purpose(prompt: str) -> dict:
|
|
1337 |
'all_constraint_scores': constraint_scores
|
1338 |
}
|
1339 |
|
1340 |
-
#
|
1341 |
def keywords(text: str, top: int = 8) -> str:
|
1342 |
words = re.findall(r'\b[a-zA-Z가-힣]{2,}\b', text.lower())
|
1343 |
stopwords = {
|
@@ -1356,7 +1357,7 @@ def keywords(text: str, top: int = 8) -> str:
|
|
1356 |
|
1357 |
def compute_relevance_scores(prompt: str, categories: list[Category]) -> dict:
|
1358 |
"""
|
1359 |
-
|
1360 |
"""
|
1361 |
prompt_lower = prompt.lower()
|
1362 |
prompt_tokens = set(re.findall(r'\b[a-zA-Z가-힣]{2,}\b', prompt_lower))
|
@@ -1380,51 +1381,41 @@ def compute_relevance_scores(prompt: str, categories: list[Category]) -> dict:
|
|
1380 |
if category.name_ko in prompt or category.name_en.lower() in prompt_lower:
|
1381 |
cat_score += 1
|
1382 |
|
1383 |
-
#
|
1384 |
if main_purpose:
|
1385 |
purpose_category_weights = {
|
1386 |
'cost_reduction': {
|
1387 |
-
# 기존 항목
|
1388 |
'구조적 변화': 1.5, '화학적 변화': 1.3, '비즈니스 아이디어': 1.5,
|
1389 |
'Structural Change': 1.5, 'Chemical Change': 1.3, 'Business Ideas': 1.5,
|
1390 |
-
# 추가 항목
|
1391 |
'에너지 변환 및 관리': 1.6, '데이터 및 정보 변환': 1.4, '지속가능성 및 환경 영향': 1.3,
|
1392 |
-
'Energy Conversion and Management': 1.6, 'Data and Information Transformation': 1.4,
|
1393 |
'Sustainability and Environmental Impact': 1.3
|
1394 |
},
|
1395 |
'innovation': {
|
1396 |
-
# 기존 항목
|
1397 |
'센서 기능': 1.5, '표면 및 외관 변화': 1.3, '비즈니스 아이디어': 1.5,
|
1398 |
'Sensor Functions': 1.5, 'Surface and Appearance Change': 1.3, 'Business Ideas': 1.5,
|
1399 |
-
# 추가 항목
|
1400 |
'사용자 인터페이스 및 상호작용': 1.6, '데이터 및 정보 변환': 1.4, '인지 및 심리적 변화': 1.3,
|
1401 |
'User Interface and Interaction': 1.6, 'Data and Information Transformation': 1.4,
|
1402 |
'Cognitive and Psychological Changes': 1.3
|
1403 |
},
|
1404 |
'risk_management': {
|
1405 |
-
# 기존 항목
|
1406 |
'환경 상호작용': 1.5, '시간 관련 변화': 1.3, '비즈니스 아이디어': 1.4,
|
1407 |
'Environmental Interaction': 1.5, 'Time-Related Change': 1.3, 'Business Ideas': 1.4,
|
1408 |
-
# 추가 항목
|
1409 |
'보안 및 프라이버시': 1.7, '지속가능성 및 환경 영향': 1.5, '데이터 및 정보 변환': 1.4,
|
1410 |
'Security and Privacy': 1.7, 'Sustainability and Environmental Impact': 1.5,
|
1411 |
'Data and Information Transformation': 1.4
|
1412 |
},
|
1413 |
'growth': {
|
1414 |
-
# 기존 항목
|
1415 |
'크기와 형태 변화': 1.4, '비즈니스 아이디어': 1.6, '구조적 변화': 1.3,
|
1416 |
'Size and Shape Change': 1.4, 'Business Ideas': 1.6, 'Structural Change': 1.3,
|
1417 |
-
# 추가 항목
|
1418 |
'사회적 상호작용 및 협업': 1.5, '데이터 및 정보 변환': 1.4, '사용자 인터페이스 및 상호작용': 1.3,
|
1419 |
'Social Interaction and Collaboration': 1.5, 'Data and Information Transformation': 1.4,
|
1420 |
'User Interface and Interaction': 1.3
|
1421 |
},
|
1422 |
'customer': {
|
1423 |
-
# 기존 항목
|
1424 |
'표면 및 외관 변화': 1.5, '센서 기능': 1.4, '빛과 시각 효과': 1.3, '비즈니스 아이디어': 1.4,
|
1425 |
'Surface and Appearance Change': 1.5, 'Sensor Functions': 1.4,
|
1426 |
'Light and Visual Effects': 1.3, 'Business Ideas': 1.4,
|
1427 |
-
# 추가 항목
|
1428 |
'사용자 인터페이스 및 상호작용': 1.7, '미학 및 감성 경험': 1.6, '인지 및 심리적 변화': 1.5,
|
1429 |
'사회적 상호작용 및 협업': 1.4,
|
1430 |
'User Interface and Interaction': 1.7, 'Aesthetics and Emotional Experience': 1.6,
|
@@ -1436,7 +1427,7 @@ def compute_relevance_scores(prompt: str, categories: list[Category]) -> dict:
|
|
1436 |
elif category.name_en in purpose_category_weights.get(main_purpose, {}):
|
1437 |
cat_score *= purpose_category_weights[main_purpose][category.name_en]
|
1438 |
|
1439 |
-
#
|
1440 |
for item in category.items:
|
1441 |
item_score = cat_score
|
1442 |
item_tokens = set(re.findall(r'\b[a-zA-Z가-힣]{2,}\b', item.lower()))
|
@@ -1458,8 +1449,7 @@ def generate_comparison_matrix(
|
|
1458 |
relevance_threshold: float = 0.2
|
1459 |
) -> list[tuple]:
|
1460 |
"""
|
1461 |
-
|
1462 |
-
(본래 의사결정 매트릭스였으나, 디자인/발명에 맞게 재활용)
|
1463 |
"""
|
1464 |
if relevance_scores is None:
|
1465 |
pool = [(c.name_ko, item) for c in categories for item in c.items]
|
@@ -1496,10 +1486,10 @@ def generate_comparison_matrix(
|
|
1496 |
evaluated_combinations.sort(key=lambda x: x[3], reverse=True)
|
1497 |
return evaluated_combinations[:max_combinations]
|
1498 |
|
1499 |
-
#
|
1500 |
def smart_weight(cat_name, item, relevance, global_cnt, T):
|
1501 |
rare_boost = 1 / (global_cnt.get(item, 0) + 0.5)
|
1502 |
-
noise = random.random() ** (1 / T) # T
|
1503 |
relevance_weight = 1 - (T - 0.1) / 3.0
|
1504 |
return ((relevance * relevance_weight) + 0.1) * rare_boost * noise
|
1505 |
|
@@ -1514,8 +1504,8 @@ def generate_random_comparison_matrix(
|
|
1514 |
T: float = 1.3,
|
1515 |
):
|
1516 |
"""
|
1517 |
-
|
1518 |
-
|
1519 |
"""
|
1520 |
if seed is None:
|
1521 |
seed = random.randrange(2 ** 32)
|
@@ -1557,9 +1547,8 @@ def generate_random_comparison_matrix(
|
|
1557 |
combos.sort(key=lambda x: x[3], reverse=True)
|
1558 |
return combos[:max_combos]
|
1559 |
|
1560 |
-
#
|
1561 |
PHYS_CATEGORIES: list[Category] = [
|
1562 |
-
# 기존 카테고리 유지
|
1563 |
Category(
|
1564 |
name_ko="센서 기능",
|
1565 |
name_en="Sensor Functions",
|
@@ -1656,8 +1645,6 @@ PHYS_CATEGORIES: list[Category] = [
|
|
1656 |
tags=["business", "idea", "비즈니스"],
|
1657 |
items=physical_transformation_categories["비즈니스 아이디어"]
|
1658 |
),
|
1659 |
-
|
1660 |
-
# 새로 추가된 카테고리
|
1661 |
Category(
|
1662 |
name_ko="사용자 인터페이스 및 상호작용",
|
1663 |
name_en="User Interface and Interaction",
|
@@ -1707,16 +1694,114 @@ PHYS_CATEGORIES: list[Category] = [
|
|
1707 |
items=physical_transformation_categories["미학 및 감성 경험"]
|
1708 |
)
|
1709 |
]
|
1710 |
-
# ──────────────────────────────── (중간 부분 생략 없이) ──────────────────────────
|
1711 |
|
1712 |
-
|
1713 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1714 |
"""
|
1715 |
-
|
1716 |
-
- 사용자 요청: "가장 우수한 10가지 아이디어"를 상세 설명
|
1717 |
-
- 결과 출력에 '가장 우수한 10가지 아이디어'에 포함되지 않은 '부가 아이디어' 30가지 리스트(한줄씩)도 설명
|
1718 |
-
- 결과 출력 시, 이미지 생성 자동화
|
1719 |
-
- Kaggle + 웹 검색 출처 제시
|
1720 |
"""
|
1721 |
cat_clause = (
|
1722 |
f'\n**추가 지침**: 선택된 카테고리 "{selected_category}"를 특별히 우선하여 고려하세요.\n'
|
@@ -1733,7 +1818,7 @@ def get_idea_system_prompt(selected_category: str | None = None,
|
|
1733 |
framework_instruction += "- Porter의 5 Forces\n"
|
1734 |
elif fw == "bcg":
|
1735 |
framework_instruction += "- BCG 매트릭스\n"
|
1736 |
-
|
1737 |
base_prompt = f"""
|
1738 |
당신은 창의적 디자인/발명 전문가 AI입니다.
|
1739 |
사용자가 입력한 주제를 분석하여,
|
@@ -1741,15 +1826,13 @@ def get_idea_system_prompt(selected_category: str | None = None,
|
|
1741 |
각 아이디어는 다음 요구를 충족해야 합니다:
|
1742 |
1) **아주 상세하게** 설명하여, 독자가 머릿속에 이미지를 그릴 수 있을 정도로 구체적으로 서술
|
1743 |
2) **이미지 프롬프트**도 함께 제시하여, 자동 이미지 생성이 되도록 하라
|
1744 |
-
- 예: `### 이미지 프롬프트\\n한 줄 영문 문구`
|
1745 |
3) **Kaggle 데이터셋**, **웹 검색**을 활용한 통찰(또는 참조)이 있으면 반드시 결과에 언급
|
1746 |
4) 최종 출력의 마지막에 **"출처"** 섹션을 만들고,
|
1747 |
- 웹 검색(Brave)에서 참조한 URL 3~5개
|
1748 |
- Kaggle 데이터셋 이름/URL(있다면)
|
1749 |
- 그 밖의 참고 자료
|
1750 |
-
5)
|
1751 |
-
|
1752 |
-
|
1753 |
{framework_instruction}
|
1754 |
|
1755 |
## 아이디어 평가 기준
|
@@ -1802,12 +1885,10 @@ def get_idea_system_prompt(selected_category: str | None = None,
|
|
1802 |
* 약점(Weaknesses): 잠재적 약점 3가지 이상 및 이를 극복하기 위한 구체적인 방안
|
1803 |
* 기회(Opportunities): 외부 환경(기술, 시장, 정책 등)에서 발생하는 기회 요소 4가지 이상
|
1804 |
* 위협(Threats): 성공을 방해할 수 있는 외부 요인 3가지 이상과 각각에 대한 구체적 대응책
|
1805 |
-
|
1806 |
-
- 각 아이디어는 이 구조로 10개 아이디어 모두 동일하게 작성하라:
|
1807 |
-
|
1808 |
4. **부가적 통찰** (선택된 프레임워크 분석 결과)
|
1809 |
-
5.
|
1810 |
-
- 예: `#### 부가 아이디어 X:\\n 한 줄로 자세한 한글 문구`
|
1811 |
6. **출처** (웹검색 링크, Kaggle 데이터셋 등)
|
1812 |
{cat_clause}
|
1813 |
아무리 길어도 이 요구사항을 준수하고, **오직 최종 완성된 답변**만 출력하십시오.
|
@@ -1815,12 +1896,12 @@ def get_idea_system_prompt(selected_category: str | None = None,
|
|
1815 |
"""
|
1816 |
return base_prompt.strip()
|
1817 |
|
1818 |
-
# ──────────────────────────────── 나머지 코드 (웹검색, kaggle, 이미지 생성 등) ──────────────────────────
|
1819 |
|
|
|
1820 |
@st.cache_data(ttl=3600)
|
1821 |
def brave_search(query: str, count: int = 20):
|
1822 |
if not BRAVE_KEY:
|
1823 |
-
raise RuntimeError("⚠️ SERPHOUSE_API_KEY (Brave API Key)
|
1824 |
headers = {
|
1825 |
"Accept": "application/json",
|
1826 |
"Accept-Encoding": "gzip",
|
@@ -1868,7 +1949,7 @@ def do_web_search(query: str) -> str:
|
|
1868 |
try:
|
1869 |
arts = brave_search(query, 20)
|
1870 |
if not arts:
|
1871 |
-
logging.warning("No search results
|
1872 |
return mock_results(query)
|
1873 |
hdr = "# Web Search Results\nUse the information below to spark new design/invention insights.\n\n"
|
1874 |
body = "\n".join(
|
@@ -1880,53 +1961,20 @@ def do_web_search(query: str) -> str:
|
|
1880 |
logging.error(f"Web search process failed: {str(e)}")
|
1881 |
return mock_results(query)
|
1882 |
|
1883 |
-
# ──────────────────────────────── (신규) 디자인/발명 아이디어 처리 함수 ─────────────────
|
1884 |
-
def process_invention_ideas(keyword: str):
|
1885 |
-
"""
|
1886 |
-
(이전에는 별도 버튼/프롬프트가 있었으나,
|
1887 |
-
이제 메인 프롬프트로 일원화되어 사실상 사용되지 않을 수도 있음.)
|
1888 |
-
"""
|
1889 |
-
if not keyword.strip():
|
1890 |
-
st.warning("키워드를 입력하세요.")
|
1891 |
-
return
|
1892 |
-
|
1893 |
-
st.info(f"디자인/발명 아이디어 생성 중... (키워드: **{keyword}**)")
|
1894 |
-
|
1895 |
-
# 모든 카테고리와 항목을 리스트업
|
1896 |
-
categories_text = []
|
1897 |
-
for cat_name, items in physical_transformation_categories.items():
|
1898 |
-
joined_items = ", ".join(items)
|
1899 |
-
categories_text.append(f"- {cat_name}: {joined_items}")
|
1900 |
-
categories_joined = "\n".join(categories_text)
|
1901 |
-
|
1902 |
-
prompt = f"""
|
1903 |
-
당신은 디자인/발명 전문가입니다.
|
1904 |
-
키워드: "{keyword}"
|
1905 |
-
아래는 카테고리+항목 목록입니다.
|
1906 |
-
{categories_joined}
|
1907 |
-
|
1908 |
-
이 키워드를 각 항목과 결합한 아이디어를 생각하고,
|
1909 |
-
타당한 것과 배제할 것을 분류하여 마크다운으로 출력하세요.
|
1910 |
-
"""
|
1911 |
-
try:
|
1912 |
-
client = get_openai_client()
|
1913 |
-
with st.spinner("Generating invention ideas..."):
|
1914 |
-
response = client.chat.completions.create(
|
1915 |
-
model="gpt-4.1-mini",
|
1916 |
-
messages=[{"role": "user", "content": prompt}],
|
1917 |
-
temperature=0.9,
|
1918 |
-
max_tokens=2500,
|
1919 |
-
)
|
1920 |
-
result_text = response.choices[0].message.content
|
1921 |
-
st.markdown(result_text)
|
1922 |
-
except Exception as e:
|
1923 |
-
st.error(f"오류 발생: {e}")
|
1924 |
|
1925 |
-
#
|
|
|
1926 |
def idea_generator_app():
|
1927 |
-
|
1928 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
1929 |
|
|
|
1930 |
default_vals = {
|
1931 |
"ai_model": "gpt-4.1-mini",
|
1932 |
"messages": [],
|
@@ -1943,9 +1991,14 @@ def idea_generator_app():
|
|
1943 |
st.session_state[k] = v
|
1944 |
|
1945 |
sb = st.sidebar
|
|
|
|
|
|
|
|
|
|
|
1946 |
st.session_state.temp = sb.slider(
|
1947 |
-
"Diversity
|
1948 |
-
help="0.1 =
|
1949 |
)
|
1950 |
|
1951 |
sb.title("Settings")
|
@@ -1968,24 +2021,17 @@ def idea_generator_app():
|
|
1968 |
sb.error("⚠️ KAGGLE_KEY not set.")
|
1969 |
st.session_state.kaggle_enabled = False
|
1970 |
|
1971 |
-
#
|
1972 |
-
|
1973 |
-
# 예시 주제
|
1974 |
-
example_topics = {
|
1975 |
-
"example1": "'고양이 장난감' 디자인",
|
1976 |
-
"example2": "재밍 대응 가능한 드론 디자인",
|
1977 |
-
"example3": "사용자 인터페이스(UI/UX) 혁신을 위한 웨어러블 기기 아이디어"
|
1978 |
-
}
|
1979 |
sb.subheader("Example Topics")
|
1980 |
c1, c2, c3 = sb.columns(3)
|
1981 |
-
if c1.button("
|
1982 |
-
process_example(
|
1983 |
-
if c2.button("
|
1984 |
-
process_example(
|
1985 |
-
if c3.button("UI/UX
|
1986 |
-
process_example(
|
1987 |
-
|
1988 |
-
#
|
1989 |
latest_ideas = next(
|
1990 |
(m["content"] for m in reversed(st.session_state.messages)
|
1991 |
if m["role"] == "assistant" and m["content"].strip()),
|
@@ -2001,12 +2047,12 @@ def idea_generator_app():
|
|
2001 |
d2.download_button("Download as HTML", md_to_html(latest_ideas, title),
|
2002 |
file_name=f"{title}.html", mime="text/html")
|
2003 |
|
2004 |
-
#
|
2005 |
up = sb.file_uploader("Load Conversation (.json)", type=["json"], key="json_uploader")
|
2006 |
if up:
|
2007 |
try:
|
2008 |
st.session_state.messages = json.load(up)
|
2009 |
-
sb.success("Conversation history loaded
|
2010 |
except Exception as e:
|
2011 |
sb.error(f"Failed to load: {e}")
|
2012 |
|
@@ -2018,7 +2064,7 @@ def idea_generator_app():
|
|
2018 |
mime="application/json"
|
2019 |
)
|
2020 |
|
2021 |
-
#
|
2022 |
st.subheader("File Upload (Optional)")
|
2023 |
uploaded_files = st.file_uploader(
|
2024 |
"Upload reference files (txt, csv, pdf)",
|
@@ -2051,7 +2097,7 @@ def idea_generator_app():
|
|
2051 |
if idx < len(uploaded_files) - 1:
|
2052 |
st.divider()
|
2053 |
|
2054 |
-
#
|
2055 |
skip_idx = st.session_state.get("_skip_dup_idx")
|
2056 |
for i, m in enumerate(st.session_state.messages):
|
2057 |
if skip_idx is not None and i == skip_idx:
|
@@ -2062,24 +2108,23 @@ def idea_generator_app():
|
|
2062 |
st.image(m["image"], caption=m.get("image_caption", ""))
|
2063 |
st.session_state["_skip_dup_idx"] = None
|
2064 |
|
2065 |
-
#
|
2066 |
-
prompt = st.chat_input("
|
2067 |
if prompt:
|
2068 |
process_input(prompt, uploaded_files)
|
2069 |
|
2070 |
sb.markdown("---")
|
2071 |
sb.markdown("Created by [VIDraft](https://discord.gg/openfreeai)")
|
2072 |
|
|
|
2073 |
def process_example(topic):
|
2074 |
process_input(topic, [])
|
2075 |
|
|
|
2076 |
def process_input(prompt: str, uploaded_files):
|
2077 |
"""
|
2078 |
-
|
2079 |
-
스트리밍 실패(RemoteProtocolError 등) 시 backoff 재시도 후
|
2080 |
-
최종적으로 non-stream 호출로 폴백.
|
2081 |
"""
|
2082 |
-
# ─── 대화 기록 중복 방지 ──────────────────────────────
|
2083 |
if not any(m["role"] == "user" and m["content"] == prompt for m in st.session_state.messages):
|
2084 |
st.session_state.messages.append({"role": "user", "content": prompt})
|
2085 |
with st.chat_message("user"):
|
@@ -2091,7 +2136,6 @@ def process_input(prompt: str, uploaded_files):
|
|
2091 |
and st.session_state.messages[i + 1]["role"] == "assistant"):
|
2092 |
return
|
2093 |
|
2094 |
-
# ─── 결과 생성 ───────────────────────────────────────
|
2095 |
with st.chat_message("assistant"):
|
2096 |
status = st.status("Preparing to generate invention ideas…")
|
2097 |
stream_placeholder = st.empty()
|
@@ -2103,10 +2147,18 @@ def process_input(prompt: str, uploaded_files):
|
|
2103 |
|
2104 |
selected_cat = st.session_state.get("category_focus", None)
|
2105 |
selected_frameworks = st.session_state.get("selected_frameworks", [])
|
2106 |
-
|
2107 |
-
|
2108 |
-
|
2109 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2110 |
|
2111 |
def category_context(sel):
|
2112 |
if sel:
|
@@ -2119,45 +2171,45 @@ def process_input(prompt: str, uploaded_files):
|
|
2119 |
|
2120 |
search_content = kaggle_content = file_content = mil_content = None
|
2121 |
|
2122 |
-
#
|
2123 |
if use_web_search:
|
2124 |
status.update(label="Searching the web…")
|
2125 |
with st.spinner("Searching…"):
|
2126 |
search_content = do_web_search(keywords(prompt, top=5))
|
2127 |
|
2128 |
-
#
|
2129 |
if use_kaggle and check_kaggle_availability():
|
2130 |
-
status.update(label="Kaggle
|
2131 |
with st.spinner("Searching Kaggle…"):
|
2132 |
kaggle_kw = extract_kaggle_search_keywords(prompt)
|
2133 |
try:
|
2134 |
datasets = search_kaggle_datasets(kaggle_kw)
|
2135 |
except Exception as e:
|
2136 |
-
logging.warning(f"search_kaggle_datasets
|
2137 |
datasets = []
|
2138 |
analyses = []
|
2139 |
if datasets:
|
2140 |
-
status.update(label="Downloading &
|
2141 |
for ds in datasets:
|
2142 |
try:
|
2143 |
ana = download_and_analyze_dataset(ds["ref"])
|
2144 |
except Exception as e:
|
2145 |
-
logging.error(f"Kaggle
|
2146 |
-
ana = f"
|
2147 |
analyses.append({"meta": ds, "analysis": ana})
|
2148 |
if analyses:
|
2149 |
kaggle_content = format_kaggle_analysis_markdown_multi(analyses)
|
2150 |
|
2151 |
-
#
|
2152 |
if has_uploaded:
|
2153 |
status.update(label="Reading uploaded files…")
|
2154 |
with st.spinner("Processing files…"):
|
2155 |
file_content = process_uploaded_files(uploaded_files)
|
2156 |
|
2157 |
-
#
|
2158 |
if is_military_query(prompt):
|
2159 |
-
status.update(label="Searching military tactics
|
2160 |
-
with st.spinner("
|
2161 |
mil_rows = military_search(prompt)
|
2162 |
if mil_rows:
|
2163 |
mil_content = "# Military Tactics Dataset Reference\n\n"
|
@@ -2169,18 +2221,16 @@ def process_input(prompt: str, uploaded_files):
|
|
2169 |
f"**Defense Reasoning:** {row['defense_reasoning']}\n\n---\n"
|
2170 |
)
|
2171 |
|
2172 |
-
# ─── 유저 콘텐츠 구성 ───────────────────────���──
|
2173 |
user_content = prompt
|
2174 |
for extra in (search_content, kaggle_content, file_content, mil_content):
|
2175 |
if extra:
|
2176 |
user_content += "\n\n" + extra
|
2177 |
|
2178 |
-
|
2179 |
-
status.update(label="분석 중…")
|
2180 |
decision_purpose = identify_decision_purpose(prompt)
|
2181 |
relevance_scores = compute_relevance_scores(prompt, PHYS_CATEGORIES)
|
2182 |
|
2183 |
-
status.update(label="
|
2184 |
T = st.session_state.temp
|
2185 |
k_cat_range = (4, 8) if T < 1.0 else (6, 10) if T < 2.0 else (8, 12)
|
2186 |
n_item_range = (2, 4) if T < 1.0 else (3, 6) if T < 2.0 else (4, 8)
|
@@ -2195,22 +2245,22 @@ def process_input(prompt: str, uploaded_files):
|
|
2195 |
T=T,
|
2196 |
)
|
2197 |
|
2198 |
-
combos_table = "|
|
2199 |
for w, imp, conf, tot, cmb in combos:
|
2200 |
combo_str = " + ".join(f"{c[0]}-{c[1]}" for c in cmb)
|
2201 |
combos_table += f"| {combo_str} | {w} | {imp} | {conf:.1f} | {tot} |\n"
|
2202 |
|
2203 |
-
purpose_info = "\n\n##
|
2204 |
if decision_purpose['purposes']:
|
2205 |
-
purpose_info += "###
|
2206 |
for p, s in decision_purpose['purposes']:
|
2207 |
-
purpose_info += f"- **{p}** (
|
2208 |
if decision_purpose['constraints']:
|
2209 |
-
purpose_info += "\n###
|
2210 |
for c, s in decision_purpose['constraints']:
|
2211 |
-
purpose_info += f"- **{c}** (
|
2212 |
|
2213 |
-
#
|
2214 |
framework_contents = []
|
2215 |
for fw in selected_frameworks:
|
2216 |
if fw == "swot":
|
@@ -2227,11 +2277,11 @@ def process_input(prompt: str, uploaded_files):
|
|
2227 |
)
|
2228 |
|
2229 |
if framework_contents:
|
2230 |
-
user_content += "\n\n## (Optional)
|
2231 |
|
2232 |
-
user_content += f"\n\n##
|
2233 |
|
2234 |
-
status.update(label="Generating final
|
2235 |
|
2236 |
api_messages = [
|
2237 |
{"role": "system", "content": sys_prompt},
|
@@ -2239,7 +2289,6 @@ def process_input(prompt: str, uploaded_files):
|
|
2239 |
{"role": "user", "content": user_content},
|
2240 |
]
|
2241 |
|
2242 |
-
# ─── OpenAI Chat 호출 (backoff 재시도) ─────────────────
|
2243 |
@backoff.on_exception(
|
2244 |
(RemoteProtocolError, APITimeoutError, APIError), max_tries=3
|
2245 |
)
|
@@ -2253,7 +2302,6 @@ def process_input(prompt: str, uploaded_files):
|
|
2253 |
stream=True
|
2254 |
)
|
2255 |
|
2256 |
-
|
2257 |
try:
|
2258 |
stream = safe_stream()
|
2259 |
for chunk in stream:
|
@@ -2261,7 +2309,7 @@ def process_input(prompt: str, uploaded_files):
|
|
2261 |
full_response += chunk.choices[0].delta.content
|
2262 |
stream_placeholder.markdown(full_response + "▌")
|
2263 |
except (RemoteProtocolError, APITimeoutError, APIError) as stream_err:
|
2264 |
-
logging.warning(f"
|
2265 |
resp = client.chat.completions.create(
|
2266 |
model="gpt-4.1-mini",
|
2267 |
messages=api_messages,
|
@@ -2275,7 +2323,7 @@ def process_input(prompt: str, uploaded_files):
|
|
2275 |
|
2276 |
status.update(label="Invention ideas created!", state="complete")
|
2277 |
|
2278 |
-
#
|
2279 |
img_data = img_caption = None
|
2280 |
if st.session_state.generate_image and full_response:
|
2281 |
match = re.search(r"###\s*이미지\s*프롬프트\s*\n+([^\n]+)", full_response, re.I)
|
@@ -2288,15 +2336,13 @@ def process_input(prompt: str, uploaded_files):
|
|
2288 |
if img_data:
|
2289 |
st.image(img_data, caption=f"Visualized Concept – {img_caption}")
|
2290 |
|
2291 |
-
# ─── 세션 메시지 저장 ─────────────────────────────
|
2292 |
answer_msg = {"role": "assistant", "content": full_response}
|
2293 |
if img_data:
|
2294 |
-
answer_msg["image"]
|
2295 |
answer_msg["image_caption"] = img_caption
|
2296 |
st.session_state["_skip_dup_idx"] = len(st.session_state.messages)
|
2297 |
st.session_state.messages.append(answer_msg)
|
2298 |
|
2299 |
-
# ─── 다운로드 옵션 ──────────────────────────────
|
2300 |
st.subheader("Download This Output")
|
2301 |
col_md, col_html = st.columns(2)
|
2302 |
col_md.download_button(
|
@@ -2319,12 +2365,11 @@ def process_input(prompt: str, uploaded_files):
|
|
2319 |
|
2320 |
except Exception as e:
|
2321 |
logging.error("process_input error", exc_info=True)
|
2322 |
-
st.error(f"⚠️
|
2323 |
st.session_state.messages.append(
|
2324 |
-
{"role": "assistant", "content": f"⚠️
|
2325 |
)
|
2326 |
|
2327 |
-
|
2328 |
def main():
|
2329 |
idea_generator_app()
|
2330 |
|
|
|
25 |
import glob
|
26 |
import shutil
|
27 |
|
28 |
+
# ─── Additional libraries (must not be removed) ───────────────────────
|
29 |
import pyarrow.parquet as pq
|
30 |
from sklearn.feature_extraction.text import TfidfVectorizer
|
31 |
from sklearn.metrics.pairwise import cosine_similarity
|
32 |
|
33 |
+
# ─── Network stability libraries ──────────────────────────────────────
|
34 |
import httpx
|
35 |
from httpx import RemoteProtocolError
|
36 |
|
37 |
+
# ▸ If `backoff` is not installed, provide a quick fallback
|
38 |
try:
|
39 |
import backoff
|
40 |
except ImportError:
|
41 |
+
logging.warning("`backoff` module is missing. Using a simple fallback decorator.")
|
42 |
|
43 |
def _simple_backoff_on_exception(exceptions, *args, **kwargs):
|
44 |
"""
|
45 |
+
Lightweight exponential (base^n) retry decorator.
|
46 |
+
This mimics the core parameters of backoff.on_exception:
|
47 |
+
- exceptions : tuple or single exception type
|
48 |
+
- max_tries : from kwargs (default 3)
|
49 |
+
- base : from kwargs (default 2)
|
|
|
50 |
"""
|
51 |
max_tries = kwargs.get("max_tries", 3)
|
52 |
base = kwargs.get("base", 2)
|
|
|
63 |
raise
|
64 |
sleep = base ** attempt
|
65 |
logging.info(
|
66 |
+
f"[retry {attempt}/{max_tries}] {fn.__name__} -> {e} … waiting {sleep}s"
|
67 |
)
|
68 |
time.sleep(sleep)
|
69 |
return wrapper
|
|
|
75 |
backoff = _DummyBackoff()
|
76 |
|
77 |
|
78 |
+
# ─────────────────────────────────── Environment Variables / Constants ─────────────────────────
|
79 |
|
80 |
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "")
|
81 |
BRAVE_KEY = os.getenv("SERPHOUSE_API_KEY", "") # Brave Search API
|
|
|
84 |
KAGGLE_API_KEY = KAGGLE_KEY
|
85 |
|
86 |
if not (KAGGLE_USERNAME and KAGGLE_KEY):
|
87 |
+
raise RuntimeError("⚠️ Please set KAGGLE_USERNAME and KAGGLE_KEY environment variables first.")
|
88 |
|
89 |
os.environ["KAGGLE_USERNAME"] = KAGGLE_USERNAME
|
90 |
os.environ["KAGGLE_KEY"] = KAGGLE_KEY
|
91 |
|
92 |
BRAVE_ENDPOINT = "https://api.search.brave.com/res/v1/web/search"
|
93 |
+
IMAGE_API_URL = "http://211.233.58.201:7896" # Example image generation API
|
94 |
+
MAX_TOKENS = 7999 # For safe token usage
|
95 |
|
96 |
+
# ────────────────────────────────── Logging ─────────────────────────────────
|
97 |
logging.basicConfig(
|
98 |
level=logging.INFO,
|
99 |
format="%(asctime)s - %(levelname)s - %(message)s"
|
100 |
)
|
101 |
|
102 |
|
103 |
+
# ─────────────────────────────── Military Dataset ─────────────────────────
|
104 |
@st.cache_resource
|
105 |
def load_military_dataset():
|
106 |
"""
|
|
|
120 |
MIL_DF = load_military_dataset()
|
121 |
|
122 |
def is_military_query(text: str) -> bool:
|
123 |
+
"""Return True if text includes military/tactical keywords."""
|
124 |
kw = [
|
125 |
"군사", "전술", "전투", "전쟁", "작전", "무기", "병력",
|
126 |
"military", "tactic", "warfare", "battle", "operation"
|
|
|
129 |
|
130 |
def military_search(query: str, top_k: int = 3):
|
131 |
"""
|
132 |
+
Use cosine similarity with scenario_description in mil.parquet
|
133 |
+
Return top K scenarios.
|
134 |
"""
|
135 |
if MIL_DF is None:
|
136 |
return []
|
|
|
148 |
logging.error(f"military_search error: {e}")
|
149 |
return []
|
150 |
|
151 |
+
|
152 |
+
# ───────────────────────────── Kaggle Datasets ───────────────────────────
|
153 |
KAGGLE_DATASETS = {
|
154 |
"general_business": {
|
155 |
"ref": "mohammadgharaei77/largest-2000-global-companies",
|
|
|
417 |
}
|
418 |
}
|
419 |
|
420 |
+
|
421 |
SUN_TZU_STRATEGIES = [
|
422 |
{"계": "만천과해", "요약": "평범한 척, 몰래 진행", "조건": "상대가 지켜보고 있을 때", "행동": "루틴·평온함 과시", "목적": "경계 무력화", "예시": "규제기관 눈치 보는 신사업 파일럿"},
|
423 |
{"계": "위위구조", "요약": "뒤통수 치면 포위 풀린다", "조건": "우리 측이 압박받을 때", "행동": "적 본진 급습", "목적": "압박 해소", "예시": "경쟁사 핵심 고객 뺏기"},
|
|
|
459 |
|
460 |
physical_transformation_categories = {
|
461 |
"센서 기능": [
|
462 |
+
# Original items
|
463 |
"시각 센서", "시각 감지", "청각 센서", "청각 감지", "촉각 센서", "촉각 감지",
|
464 |
"미각 센서", "미각 감지", "후각 센서", "후각 감지", "온도 센서", "온도 감지",
|
465 |
"습도 센서", "습도 감지", "압력 센서", "압력 감지", "가속도 센서", "가속도 감지",
|
|
|
477 |
"홀 효과 감지", "초음파 센서", "초음파 감지", "레이더 센서", "레이더 감지",
|
478 |
"라이다 센서", "라이다 감지", "터치 센서", "터치 감지", "제스처 센서", "제스처 감지",
|
479 |
"심박 센서", "심박 감지", "혈압 센서", "혈압 감지", "LAN", "WIFI", "블루투스", "생체 인증",
|
480 |
+
# Additional items
|
481 |
"다중 스펙트럼 센서", "다중 스펙트럼 감지", "깊이 인식 센서", "깊이 인식 감지",
|
482 |
"퀀텀 센서", "퀀텀 감지", "웨어러블 센서", "웨어러블 감지", "바이오마커 센서", "바이오마커 감지",
|
483 |
"임베디드 센서", "임베디드 감지", "IoT 센서 네트워크", "스트레인 센서", "스트레인 감지",
|
|
|
485 |
"스마트 먼지 센서", "환경 센서 그리드", "신경형태학적 센서", "두뇌-기계 인터페이스"
|
486 |
],
|
487 |
"크기와 형태 변화": [
|
488 |
+
# Original items
|
489 |
"부피 늘어남", "부피 줄어듦", "길이 늘어남", "길이 줄어듦", "너비 늘어남", "너비 줄어남",
|
490 |
"높이 늘어남", "높이 줄어듦", "밀도 변화", "무게 증가", "무게 감소", "모양 변형",
|
491 |
"상태 변화", "불균등 변형", "복잡한 형태 변형", "비틀림", "꼬임", "불균일한 확장",
|
|
|
493 |
"물 저항", "먼지 저항", "찌그러짐", "복원", "접힘", "펼쳐짐", "압착", "팽창",
|
494 |
"늘어남", "수축", "구겨짐", "평평해짐", "뭉개짐", "단단해짐", "말림", "펴짐",
|
495 |
"꺾임", "구부러짐",
|
496 |
+
# Additional items
|
497 |
"4D 프린팅 변형", "형상 기억", "프랙탈 변화", "자가 조립", "자가 복구",
|
498 |
"기하학적 변환", "모듈화", "스마트 직물 변형", "매트릭스 구조 변형", "프로그래머블 변형",
|
499 |
"미시 스케일 변형", "거시 스케일 변형", "이방성 변형", "등방성 변형", "선택적 강성 변화",
|
500 |
"변형률 감응 구조", "형태학적 계산", "위상 변화", "경도 변화", "부드러움 변화"
|
501 |
],
|
502 |
"표면 및 외관 변화": [
|
503 |
+
# Original items
|
504 |
"색상 변화", "질감 변화", "투명 변화", "불투명 변화", "반짝임 변화", "무광 변화",
|
505 |
"빛 반사 정도 변화", "무늬 변화", "각도에 따른 색상 변화", "빛에 따른 색상 변화",
|
506 |
"온도에 따른 색상 변화", "홀로그램 효과", "표면 각도별 빛 반사", "표면 모양 변형",
|
|
|
508 |
"선명함 변화", "광택 변화", "윤기 변화", "색조 변화", "채도 변화", "발광",
|
509 |
"형광", "빛 산란 효과", "빛 흡수 변화", "반투명 효과", "그림자 효과 변화",
|
510 |
"자외선 반응 변화", "야광 효과",
|
511 |
+
# Additional items
|
512 |
"생체모방 표면", "프로그래머블 질감", "촉각 피드백 표면", "열 반응성 표면",
|
513 |
"초소수성/초친수성 표면", "스마트 코팅", "마찰 계수 변화", "도금 효과", "위장 효과",
|
514 |
"양자점 효과", "메타표면 효과", "나노 구조화 표면", "전기변색 효과", "광변색 효과",
|
515 |
+
"압력변색 효과", "자기변색 효과", "항균 표면", "공기역학적 표면", "자기정렬 패턴",
|
516 |
"부착성 변화", "선택적 접착성"
|
517 |
],
|
518 |
"물질의 상태 변화": [
|
519 |
+
# Original items
|
520 |
"고체 전환", "액체 전환", "기체 전환", "결정화", "용해", "산화", "부식",
|
521 |
"딱딱해짐", "부드러워짐", "특수 상태 전환", "무정형 전환", "결정형 전환", "성분 분리",
|
522 |
"미세 입자 형성", "미세 입자 분해", "젤 형성", "젤 풀어짐", "준안정 상태 변화",
|
|
|
524 |
"증발", "응축", "승화", "증착", "침전", "부유", "분산", "응집",
|
525 |
"건조", "습윤", "팽윤", "수축", "동결", "해동", "풍화", "침식",
|
526 |
"충전", "방전", "결합", "분리", "발효", "부패",
|
527 |
+
# Additional items
|
528 |
"초임계 상태 전환", "양자 상태 전환", "메타물질 상태 변화", "프로그래머블 물질 변화",
|
529 |
"소프트 로봇 물질 변화", "4D 프린팅 물질 변화", "바이오하이브리드 물질 변화",
|
530 |
"자가 조직화 물질", "자가 순환 물질", "자가 치유 물질", "생분해성 전환",
|
|
|
532 |
"스마트 유체 상태", "자극 반응성 상태", "형상기억 합금 상태", "초전도 상태"
|
533 |
],
|
534 |
"움직임 특성 변화": [
|
535 |
+
# Original items
|
536 |
"가속", "감속", "일정 속도 유지", "진동", "진동 감소", "부딪힘", "튕김",
|
537 |
"회전 속도 증가", "회전 속도 감소", "회전 방향 변화", "불규칙 움직임", "멈췄다", "미끄러지는 현상",
|
538 |
"공진", "반공진", "유체 속 저항 변화", "유체 속 양력 변화", "움직임 저항 변화",
|
539 |
"복합 진동 움직임", "특수 유체 속 움직임", "회전-이동 연계 움직임", "관성 정지",
|
540 |
"충격 흡수", "충격 전달", "운동량 보존", "마찰력 변화", "관성 탈출", "불안정 균형",
|
541 |
"동적 안정성", "흔들림 감쇠", "경로 예측성", "회피 움직임",
|
542 |
+
# Additional items
|
543 |
"복합 운동학", "임의의 움직임", "재귀적 움직임", "흉내내는 움직임", "학습된 움직임",
|
544 |
"자율적 움직임", "군집 움직임", "비선형 움직임", "양자 움직임", "초음파 움직임",
|
545 |
"비대칭 움직임", "스토캐스틱 움직임", "카오스 움직임", "소프트 로보틱스 움직임",
|
|
|
547 |
"계층적 움직임 제어", "적응형 움직임 패턴"
|
548 |
],
|
549 |
"구조적 변화": [
|
550 |
+
# Original items
|
551 |
"부품 추가", "부품 제거", "조립", "분해", "접기", "펴기", "변형", "원상복구",
|
552 |
"최적 구조 변화", "자가 재배열", "자연 패턴 형성", "자연 패턴 소멸", "규칙적 패턴 변화",
|
553 |
"모듈식 변형", "복잡성 증가 구조", "원래 모양 기억 효과", "시간에 따른 형태 변화",
|
|
|
555 |
"내부 구조 변화", "외부 구조 변화", "중심축 이동", "균형점 변화", "계층 구조 변화",
|
556 |
"지지 구조 변화", "응력 분산 구조", "충격 흡수 구조", "그리드 구조 변화", "매트릭스 구조 변화",
|
557 |
"상호 연결성 변화",
|
558 |
+
# Additional items
|
559 |
"텐세그리티 구조 변화", "바이오닉 구조", "메타물질 구조", "다중 안정 구조", "자가 진화 구조",
|
560 |
"자가 학습 구조", "생체모방 구조", "프랙탈 구조", "계층적 구조화", "에너지 흡수 구조",
|
561 |
"에너지 변환 구조", "적응형 구조", "위상 최적화", "다공성 구조", "기능적 경사 구조",
|
562 |
"다중 재료 구조", "초경량 구조", "초고강도 구조", "다기능성 구조", "내결함성 구조"
|
563 |
],
|
564 |
"공간 이동": [
|
565 |
+
# Original items
|
566 |
"앞 이동", "뒤 이동", "좌 이동", "우 이동", "위 이동", "아래 이동",
|
567 |
"세로축 회전(고개 끄덕임)", "가로축 회전(고개 젓기)", "길이축 회전(옆으로 기울임)", "원 운동",
|
568 |
"나선형 이동", "관성에 의한 미끄러짐", "회전축 변화", "불규칙 회전", "흔들림 운동",
|
569 |
"포물선 이동", "무중력 부유", "수면 위 부유", "점프", "도약", "슬라이딩", "롤링",
|
570 |
"자유 낙하", "왕복 운동", "탄성 튕김", "관통", "회피 움직임", "지그재그 이동", "스윙 운동",
|
571 |
+
# Additional items
|
572 |
"양자 이동", "차원 간 이동", "가상 공간 이동", "증강 공간 이동", "무인 이동",
|
573 |
"군집 이동", "경로 최적화 이동", "상황 인식 이동", "생태계 통합 이동", "바이오닉 이동",
|
574 |
"미시 스케일 이동", "매크로 스케일 이동", "변형 기반 이동", "자기장 유도 이동",
|
|
|
576 |
"지능형 경로 탐색"
|
577 |
],
|
578 |
"시간 관련 변화": [
|
579 |
+
# Original items
|
580 |
"노화", "풍화", "마모", "부식", "색 바램", "변색", "손상", "회복",
|
581 |
"수명 주기 변화", "사용자 상호작용에 따른 적응", "학습 기반 형태 최적화", "시간에 따른 물성 변화",
|
582 |
"집단 기억 효과", "문화적 의미 변화", "지연 반응", "이전 상태 의존 변화", "점진적 시간 변화",
|
583 |
"진화적 변화", "주기적 재생", "계절 변화 적응", "생체리듬 변화", "생애 주기 단계",
|
584 |
"성장", "퇴화", "자가 복구", "자가 재생", "자연 순환 적응", "지속성", "일시성",
|
585 |
"기억 효과", "지연된 작용", "누적 효과",
|
586 |
+
# Additional items
|
587 |
"시간 지연 효과", "예측 기반 변화", "학습 기반 변화", "인지적 시간 변화",
|
588 |
"시간 압축 경험", "시간 확장 경험", "시간적 패턴 감지", "시간적 패턴 생성",
|
589 |
"계절 인식 변화", "생체 시계 동기화", "시간 기반 프로그래밍", "연대기적 데이터 구조",
|
|
|
591 |
"사용 패턴 적응", "사용자 타임라인 통합", "예측 유지보수", "자가 최적화 타이밍"
|
592 |
],
|
593 |
"빛과 시각 효과": [
|
594 |
+
# Original items
|
595 |
"발광", "소등", "빛 투과", "빛 차단", "빛 산란", "빛 집중", "색상 스펙트럼 변화",
|
596 |
"빛 회절", "빛 간섭", "홀로그램 생성", "레이저 효과", "빛 편광", "형광", "인광",
|
597 |
"자외선 발광", "적외선 발광", "광학적 착시", "빛 굴절", "그림자 생성", "그림자 제거",
|
598 |
"색수차 효과", "무지개 효과", "글로우 효과", "플래시 효과", "조명 패턴", "빔 효과",
|
599 |
"광 필터 효과", "빛의 방향성 변화", "투영 효과", "빛 감지", "빛 반응", "광도 변화",
|
600 |
+
# Additional items
|
601 |
"양자 발광", "메타 광학 효과", "프로그래머블 광학", "시야각 변화", "생체발광 모방",
|
602 |
"광역학 효과", "퍼셉션 변화 효과", "맥스웰리안 뷰", "광 컴퓨팅 효과", "광유전학 효과",
|
603 |
"생체 광학 모방", "비선형 광학 효과", "구조색 변화", "자가 발광", "광 결정 효과",
|
604 |
"양자점 방출", "나노 발광체", "증강 광학", "투명 디스플레이 효과", "광학 위장"
|
605 |
],
|
606 |
"소리와 진동 효과": [
|
607 |
+
# Original items
|
608 |
"소리 발생", "소리 소멸", "음 높낮이 변화", "음량 변화", "음색 변화", "공명",
|
609 |
"반공명", "음향 진동", "초음파 발생", "저음파 발생", "소리 집중", "소리 분산",
|
610 |
"음향 반사", "음향 흡수", "음향 도플러 효과", "음파 간섭", "음향 공진", "진동 패턴 변화",
|
611 |
"타악 효과", "음향 피드백", "음향 차폐", "음향 증폭", "소리 지향성", "소리 왜곡",
|
612 |
"비트 생성", "배음 생성", "주파수 변조", "음향 충격파", "음향 필터링",
|
613 |
+
# Additional items
|
614 |
"메타 음향 효과", "방향성 음향", "3D 음향 효과", "생체음향 모방", "상황별 음향 변화",
|
615 |
"음향 위장", "음향 투명화", "음향 렌즈", "양자 음향 효과", "초저주파 효과",
|
616 |
"초고주파 효과", "음파 에너지 수확", "자가 조절 공명", "음향 홀로그래피",
|
|
|
618 |
"기능적 음향 표면", "구조 공진 조절"
|
619 |
],
|
620 |
"열 관련 변화": [
|
621 |
+
# Original items
|
622 |
"온도 상승", "온도 하강", "열 팽창", "열 수축", "열 전달", "열 차단", "압력 상승",
|
623 |
"압력 하강", "열 변화에 따른 자화", "엔트로피 변화", "열전기 효과", "자기장에 의한 열 변화",
|
624 |
"상태 변화 중 열 저장", "상태 변화 중 열 방출", "열 스트레스 발생", "열 스트레스 해소",
|
|
|
626 |
"열 반사", "열 흡수", "냉각 응축", "열 활성화", "열 변색", "열 팽창 계수 변화",
|
627 |
"열 안정성 변화", "내열성", "내한성", "자가 발열", "열적 평형", "열적 불균형",
|
628 |
"열적 변형", "열 분산", "열 집중",
|
629 |
+
# Additional items
|
630 |
"열전 효과", "열광 효과", "프로그래머블 열 특성", "상변화 냉각", "상변화 가열",
|
631 |
"열 유도 기억", "열 광학 효과", "열 음향 효과", "열 기계 효과", "열 화학 반응",
|
632 |
"양자 열역학 효과", "근적외선 열 효과", "열 조절 표면", "열흐름 제어", "열 방출 최적화",
|
633 |
"열 캡처 최적화", "자가 조절 온도", "열 스위칭", "열 포커싱", "상변화 재료 활용"
|
634 |
],
|
635 |
"전기 및 자기 변화": [
|
636 |
+
# Original items
|
637 |
"자성 생성", "자성 소멸", "전하량 증가", "전하량 감소", "전기장 생성", "전기장 소멸",
|
638 |
"자기장 생성", "자기장 소멸", "초전도 상태 전환", "강유전체 특성 변화", "양자 상태 변화",
|
639 |
"플라즈마 형성", "플라즈마 소멸", "스핀파 전달", "빛에 의한 전기 발생", "압력에 의한 전기 발생",
|
|
|
641 |
"전자기 유도", "전자기파 방출", "전자기파 흡수", "전기 용량 변화", "자기 이력 현상",
|
642 |
"전기적 분극", "전자 흐름 방향 변화", "전기적 공명", "전기적 차폐", "전기적 노출",
|
643 |
"자기 차폐", "자기 노출", "자기장 정렬", "유선(Wire)", "무선(Wireless)",
|
644 |
+
# Additional items
|
645 |
"양자 자성", "스핀트로닉스 효과", "마그네토일렉트릭 효과", "토폴로지컬 절연체 특성",
|
646 |
"초전도 양자 효과", "쿨롱 차단 효과", "조셉슨 효과", "홀 효과 변화", "전자기 투명성",
|
647 |
"자기 카이랄리티", "전자기 메타표면", "무선 전력 전송", "자기유변학적 효과",
|
|
|
649 |
"전자 스핀 제어", "고속 스위칭 자성"
|
650 |
],
|
651 |
"화학적 변화": [
|
652 |
+
# Original items
|
653 |
"표면 코팅 변화", "물질 성분 변화", "화학 반응 변화", "촉매 작용 시작/중단",
|
654 |
"빛에 의한 화학 반응", "전기에 의한 화학 반응", "단분자막 형성", "분자 수준 구조 변화",
|
655 |
"생체 모방 표면 변화", "환경 반응형 물질 변화", "주기적 화학 반응", "산화", "환원",
|
656 |
"고분자화", "물 분해", "화합", "방사선 영향", "산-염기 반응", "중화 반응",
|
657 |
"이온화", "화학적 흡착/탈착", "촉매 효율 변화", "효소 활성 변화", "발색 반응",
|
658 |
"pH 변화", "화학적 평형 이동", "결합 형성/분해", "용해도 변화",
|
659 |
+
# Additional items
|
660 |
"프로그래머블 화학 반응", "자가 촉매 반응", "클릭 케미스트리", "광화학 반응",
|
661 |
"전기화학 반응", "초분자 화학 반응", "동적 공유 결합", "바이오오쏘고널 화학",
|
662 |
"화학적 컴퓨팅", "화학적 감지", "화학적 통신", "화학적 기억", "선택적 촉매",
|
|
|
664 |
"화학적 패턴 형성", "화학적 습도 조절", "화학적 정화"
|
665 |
],
|
666 |
"생물학적 변화": [
|
667 |
+
# Original items
|
668 |
"성장/위축", "세포 분열/사멸", "생물 발광", "신진대사 변화", "면역 반응",
|
669 |
"호르몬 분비", "신경 반응", "유전적 발현", "적응/진화", "생체리듬 변화",
|
670 |
"재생/치유", "노화/성숙", "생체 모방 변화", "바이오필름 형성", "생물학적 분해",
|
671 |
"효소 활성화/비활성화", "생물학적 신호 전달", "스트레스 반응", "체온 조절", "생물학적 시계 변화",
|
672 |
"세포외 기질 변화", "생체 역학적 반응", "세포 운동성", "세포 극성 변화", "영양 상태 변화",
|
673 |
+
# Additional items
|
674 |
"합성 생물학 반응", "생물학적 컴퓨팅", "오가노이드 발달", "인공 조직 발달",
|
675 |
"생체적합성 변화", "면역학적 응답 제어", "후성유전학적 변화", "생물학적 리듬 조절",
|
676 |
"신경가소성 효과", "세포외 기질 리모델링", "체세포 리프로그래밍", "생체활성 표면 상호작용",
|
|
|
678 |
"바이오하이브리드 시스템", "세포 분화 조절", "생���신호 증폭", "생화학적 기억 형성"
|
679 |
],
|
680 |
"환경 상호작용": [
|
681 |
+
# Original items
|
682 |
"온도 반응", "습도 반응", "기압 반응", "중력 반응", "자기장 반응",
|
683 |
"빛 반응", "소리 반응", "화학 물질 감지", "기계적 자극 감지", "전기 자극 반응",
|
684 |
"방사선 반응", "진동 감지", "pH 반응", "용매 반응", "기체 교환",
|
685 |
"환경 오염 반응", "날씨 반응", "계절 반응", "일주기 반응", "생태계 상호작용",
|
686 |
"공생/경쟁 반응", "포식/피식 관계", "군집 형성", "영역 설정", "이주 패턴", "정착 패턴",
|
687 |
+
# Additional items
|
688 |
"탄소 포집 및 변환", "생태계 복원 효과", "생물다양성 증진", "순환 경제 상호작용",
|
689 |
"도시 환경 통합", "스마트 환경 감지", "재생 가능 에너지 연계", "물 순환 상호작용",
|
690 |
"대기 질 상호작용", "자연 기반 솔루션 통합", "재해 복원력 증진", "기후 변화 적응",
|
|
|
692 |
"환경적 자가 수정", "지속가능한 자원 관리", "생태계 건강 모니터링", "생태 교란 방지"
|
693 |
],
|
694 |
"비즈니스 아이디어": [
|
695 |
+
# Original items
|
696 |
"시장 재정의/신규 시장 개척",
|
697 |
"비즈니스 모델 혁신/디지털 전환",
|
698 |
"고객 경험 혁신/서비스 혁신",
|
|
|
703 |
"지속 가능한 성장/사회적 가치 창출",
|
704 |
"데이터 기반 의사결정/AI 도입",
|
705 |
"신기술 융합/혁신 투자",
|
706 |
+
# Additional items
|
707 |
"탄소중립 비즈니스 모델",
|
708 |
"순환경제 비즈니스 모델",
|
709 |
"구독 경제 모델",
|
|
|
721 |
"웰빙/웰니스 중심 비즈니스"
|
722 |
],
|
723 |
|
724 |
+
# Newly added categories
|
725 |
|
726 |
"사용자 인터페이스 및 상호작용": [
|
727 |
"제스처 인식", "제스처 제어", "음성 인식", "음성 제어", "시선 추적", "시선 제어",
|
|
|
798 |
]
|
799 |
}
|
800 |
|
801 |
+
# ────────────────────────────── Frameworks ─────────────────────────────
|
802 |
SWOT_FRAMEWORK = {
|
803 |
"strengths": {
|
804 |
+
"title": "Strengths",
|
805 |
+
"description": "Internal positive factors – competitive advantages",
|
806 |
"prompt_keywords": ["강점", "장점", "우위", "역량", "자산", "전문성", "strength", "advantage"]
|
807 |
},
|
808 |
"weaknesses": {
|
809 |
+
"title": "Weaknesses",
|
810 |
+
"description": "Internal negative factors – internal limitations",
|
811 |
"prompt_keywords": ["약점", "단점", "부족", "한계", "취약점", "weakness", "limitation", "deficit"]
|
812 |
},
|
813 |
"opportunities": {
|
814 |
+
"title": "Opportunities",
|
815 |
+
"description": "External positive factors – potential to leverage",
|
816 |
"prompt_keywords": ["기회", "가능성", "트렌드", "변화", "성장", "opportunity", "trend", "potential"]
|
817 |
},
|
818 |
"threats": {
|
819 |
+
"title": "Threats",
|
820 |
+
"description": "External negative factors – threats or obstacles",
|
821 |
"prompt_keywords": ["위협", "리스크", "경쟁", "위험", "장벽", "threat", "risk", "competition", "barrier"]
|
822 |
}
|
823 |
}
|
824 |
|
825 |
PORTER_FRAMEWORK = {
|
826 |
"rivalry": {
|
827 |
+
"title": "Competitive Rivalry",
|
828 |
+
"description": "Intensity of competition among existing competitors",
|
829 |
"prompt_keywords": ["경쟁", "경쟁사", "시장점유율", "가격경쟁", "competition", "rival", "market share"]
|
830 |
},
|
831 |
"new_entrants": {
|
832 |
+
"title": "Threat of New Entrants",
|
833 |
+
"description": "Ease or difficulty of new players entering the market",
|
834 |
"prompt_keywords": ["진입장벽", "신규", "스타트업", "entry barrier", "newcomer", "startup"]
|
835 |
},
|
836 |
"substitutes": {
|
837 |
+
"title": "Threat of Substitutes",
|
838 |
+
"description": "Availability of substitute products/services",
|
839 |
"prompt_keywords": ["대체재", "대안", "substitute", "alternative", "replacement"]
|
840 |
},
|
841 |
"buyer_power": {
|
842 |
+
"title": "Buyer Power",
|
843 |
+
"description": "Negotiating leverage of customers",
|
844 |
"prompt_keywords": ["고객", "구매자", "가격민감도", "협상력", "customer", "buyer power"]
|
845 |
},
|
846 |
"supplier_power": {
|
847 |
+
"title": "Supplier Power",
|
848 |
+
"description": "Negotiating leverage of suppliers",
|
849 |
"prompt_keywords": ["공급자", "벤더", "원재료", "supplier", "vendor", "raw material"]
|
850 |
}
|
851 |
}
|
852 |
|
853 |
BCG_FRAMEWORK = {
|
854 |
"stars": {
|
855 |
+
"title": "Stars",
|
856 |
+
"description": "High growth, high market share – need investment",
|
857 |
"prompt_keywords": ["성장", "점유율", "중점", "투자", "star", "growth", "investment"]
|
858 |
},
|
859 |
"cash_cows": {
|
860 |
+
"title": "Cash Cows",
|
861 |
+
"description": "Low growth, high market share – generates cash flow",
|
862 |
"prompt_keywords": ["안정", "수익", "현금", "전통", "cash cow", "profit", "mature"]
|
863 |
},
|
864 |
"question_marks": {
|
865 |
+
"title": "Question Marks",
|
866 |
+
"description": "High growth, low market share – uncertain potential",
|
867 |
"prompt_keywords": ["가능성", "위험", "불확실", "잠재", "question mark", "uncertain", "potential"]
|
868 |
},
|
869 |
"dogs": {
|
870 |
+
"title": "Dogs",
|
871 |
+
"description": "Low growth, low market share – potential divest",
|
872 |
"prompt_keywords": ["회수", "철수", "저성장", "비효율", "dog", "divest", "low growth"]
|
873 |
}
|
874 |
}
|
875 |
|
876 |
BUSINESS_FRAMEWORKS = {
|
877 |
+
"sunzi": "Sun Tzu's 36 Stratagems",
|
878 |
+
"swot": "SWOT Analysis",
|
879 |
+
"porter": "Porter's 5 Forces",
|
880 |
+
"bcg": "BCG Matrix"
|
881 |
}
|
882 |
|
883 |
+
|
884 |
@dataclass
|
885 |
class Category:
|
886 |
+
"""Unified category/item structure."""
|
887 |
name_ko: str
|
888 |
name_en: str
|
889 |
tags: list[str]
|
|
|
953 |
if not analysis_result:
|
954 |
return ""
|
955 |
titles = {
|
956 |
+
'swot': '# SWOT Analysis',
|
957 |
+
'porter': '# Porter’s 5 Forces Analysis',
|
958 |
+
'bcg': '# BCG Matrix Analysis'
|
959 |
}
|
960 |
+
md = f"{titles.get(framework_type, '# Business Framework Analysis')}\n\n"
|
961 |
+
md += "Each factor includes a text analysis score and keywords found.\n\n"
|
962 |
for category, info in analysis_result.items():
|
963 |
md += f"## {info['title']}\n\n"
|
964 |
md += f"{info['description']}\n\n"
|
965 |
+
md += f"**Relevance Score**: {info['score']}\n\n"
|
966 |
if info['keywords']:
|
967 |
+
md += "**Keywords/Context**:\n"
|
968 |
for keyword in info['keywords']:
|
969 |
md += f"- *{keyword}*\n"
|
970 |
md += "\n"
|
971 |
else:
|
972 |
+
md += "No relevant keywords found.\n\n"
|
973 |
return md
|
974 |
|
975 |
+
# ───────────────────────────── Markdown -> HTML ───────────────────────
|
976 |
def md_to_html(md_text: str, title: str = "Output") -> str:
|
977 |
html_content = markdown.markdown(
|
978 |
md_text,
|
|
|
1066 |
</html>
|
1067 |
"""
|
1068 |
|
1069 |
+
# ───────────────────────────── Upload File Handling ───────────────────
|
1070 |
def process_text_file(uploaded_file):
|
1071 |
try:
|
1072 |
content = uploaded_file.read().decode('utf-8')
|
1073 |
+
return f"""# Uploaded Text File: {uploaded_file.name}
|
1074 |
|
1075 |
{content}
|
1076 |
"""
|
1077 |
except Exception as e:
|
1078 |
+
logging.error(f"Text file processing error: {str(e)}")
|
1079 |
return f"**Error processing {uploaded_file.name}**: {str(e)}"
|
1080 |
|
1081 |
def process_csv_file(uploaded_file):
|
1082 |
try:
|
1083 |
df = pd.read_csv(uploaded_file)
|
1084 |
+
return f"""# Uploaded CSV File: {uploaded_file.name}
|
1085 |
|
1086 |
+
## Basic Info
|
1087 |
+
- Rows: {df.shape[0]}
|
1088 |
+
- Columns: {df.shape[1]}
|
1089 |
+
- Column Names: {', '.join(df.columns.tolist())}
|
1090 |
|
1091 |
+
## First 5 Rows
|
1092 |
{df.head(5).to_markdown(index=False)}
|
1093 |
|
1094 |
+
## Basic Statistics
|
1095 |
{df.describe().to_markdown()}
|
1096 |
"""
|
1097 |
except Exception as e:
|
1098 |
+
logging.error(f"CSV file processing error: {str(e)}")
|
1099 |
return f"**Error processing {uploaded_file.name}**: {str(e)}"
|
1100 |
|
1101 |
def process_pdf_file(uploaded_file):
|
|
|
1110 |
pages_preview.append(f"--- Page {page_num+1} ---\n{page.extract_text()}")
|
1111 |
|
1112 |
preview_text = "\n\n".join(pages_preview)
|
1113 |
+
return f"""# Uploaded PDF File: {uploaded_file.name}
|
1114 |
|
1115 |
+
## Basic Info
|
1116 |
+
- Total Pages: {len(reader.pages)}
|
1117 |
|
1118 |
+
## Preview of First 5 Pages
|
1119 |
{preview_text}
|
1120 |
"""
|
1121 |
except Exception as e:
|
1122 |
+
logging.error(f"PDF file processing error: {str(e)}")
|
1123 |
return f"**Error processing {uploaded_file.name}**: {str(e)}"
|
1124 |
|
1125 |
def process_uploaded_files(uploaded_files):
|
|
|
1144 |
f"# Unsupported file: {file.name}\n\nThis file type is not supported for processing."
|
1145 |
)
|
1146 |
except Exception as e:
|
1147 |
+
logging.error(f"Error processing file {file.name}: {str(e)}")
|
1148 |
file_contents.append(f"# Error processing file: {file.name}\n\n{str(e)}")
|
1149 |
|
1150 |
+
return "\n\n# User Uploaded File Analysis\n\n" + "\n\n---\n\n".join(file_contents)
|
1151 |
+
|
1152 |
|
1153 |
+
# ───────────────────────────── Image Generation ──────────────────────
|
1154 |
def generate_image(prompt: str):
|
1155 |
if not prompt:
|
1156 |
return None, None
|
|
|
1183 |
logging.error(f"Image generation error: {str(e)}", exc_info=True)
|
1184 |
return None, None
|
1185 |
|
1186 |
+
# ───────────────────────────── Kaggle API ─────────────────────────────
|
1187 |
@st.cache_resource
|
1188 |
def check_kaggle_availability():
|
1189 |
if not KAGGLE_API_KEY:
|
1190 |
+
logging.warning("Kaggle API not available (KAGGLE_KEY is empty).")
|
1191 |
return False
|
1192 |
return True
|
1193 |
|
|
|
1226 |
@st.cache_data
|
1227 |
def download_and_analyze_dataset(dataset_ref: str, max_rows: int = 1000):
|
1228 |
if not (os.getenv("KAGGLE_USERNAME") and os.getenv("KAGGLE_KEY")):
|
1229 |
+
return "Missing Kaggle API credentials."
|
1230 |
api = KaggleApi()
|
1231 |
api.authenticate()
|
1232 |
tmpdir = tempfile.mkdtemp()
|
|
|
1235 |
except Exception as e:
|
1236 |
logging.error(f"Dataset download failed ({dataset_ref}): {e}")
|
1237 |
shutil.rmtree(tmpdir)
|
1238 |
+
return f"Error downloading dataset: {e}"
|
1239 |
|
1240 |
csv_files = glob.glob(f"{tmpdir}/**/*.csv", recursive=True)
|
1241 |
if not csv_files:
|
1242 |
shutil.rmtree(tmpdir)
|
1243 |
+
return "No CSV file found in the dataset."
|
1244 |
|
1245 |
try:
|
1246 |
df = pd.read_csv(csv_files[0], nrows=max_rows)
|
|
|
1252 |
"missing_values": df.isnull().sum().to_dict()
|
1253 |
}
|
1254 |
except Exception as e:
|
1255 |
+
analysis = f"CSV parsing error: {e}"
|
1256 |
|
1257 |
shutil.rmtree(tmpdir)
|
1258 |
return analysis
|
1259 |
|
1260 |
def format_kaggle_analysis_markdown_multi(analyses: list[dict]) -> str:
|
1261 |
"""
|
1262 |
+
For multiple Kaggle datasets (up to 3) meta/analysis results in a single markdown.
|
1263 |
analyses = [ {"meta": {...}, "analysis": {... or str}}, ... ]
|
1264 |
"""
|
1265 |
if not analyses:
|
1266 |
+
return "# Kaggle Datasets\n\nNo related dataset found.\n\n"
|
1267 |
+
md = "# Kaggle Dataset Analysis\n\n"
|
1268 |
+
md += "Review the following datasets for further insight:\n\n"
|
1269 |
for i, item in enumerate(analyses, 1):
|
1270 |
ds = item["meta"]
|
1271 |
ana = item["analysis"]
|
1272 |
md += f"## {i}. {ds['title']}\n\n"
|
1273 |
md += f"{ds['subtitle']}\n\n"
|
1274 |
+
md += f"- **Ref**: {ds['ref']}\n"
|
1275 |
+
md += f"- **URL**: [{ds['url']}]({ds['url']})\n\n"
|
1276 |
if isinstance(ana, dict):
|
1277 |
+
md += f"**Rows × Columns**: {ana['shape'][0]} × {ana['shape'][1]}\n\n"
|
1278 |
+
md += "<details><summary>Preview & Stats (Click to expand)</summary>\n\n"
|
1279 |
try:
|
1280 |
md += pd.DataFrame(ana["head"]).to_markdown(index=False) + "\n\n"
|
1281 |
except:
|
|
|
1290 |
md += "---\n\n"
|
1291 |
return md
|
1292 |
|
1293 |
+
# ───────────────────────────── OpenAI Client ──────────────────────────
|
1294 |
@st.cache_resource
|
1295 |
def get_openai_client():
|
1296 |
if not OPENAI_API_KEY:
|
1297 |
+
raise RuntimeError("⚠️ OPENAI_API_KEY is not set.")
|
1298 |
return OpenAI(
|
1299 |
api_key=OPENAI_API_KEY,
|
1300 |
timeout=60.0,
|
1301 |
max_retries=3
|
1302 |
)
|
1303 |
|
1304 |
+
# ───────────────────── Identify Decision Purpose (Design/Invention) ─────────────────────
|
1305 |
def identify_decision_purpose(prompt: str) -> dict:
|
1306 |
"""
|
1307 |
+
Identify main design/invention purposes or constraints from the prompt.
|
|
|
|
|
1308 |
"""
|
1309 |
purpose_patterns = {
|
1310 |
'cost_reduction': [r'비용(\s*절감)?', r'예산', r'효율', r'저렴', r'경제', r'cost', r'saving', r'budget'],
|
|
|
1338 |
'all_constraint_scores': constraint_scores
|
1339 |
}
|
1340 |
|
1341 |
+
# ───────────────────────────── Category Utility ──────────────────────
|
1342 |
def keywords(text: str, top: int = 8) -> str:
|
1343 |
words = re.findall(r'\b[a-zA-Z가-힣]{2,}\b', text.lower())
|
1344 |
stopwords = {
|
|
|
1357 |
|
1358 |
def compute_relevance_scores(prompt: str, categories: list[Category]) -> dict:
|
1359 |
"""
|
1360 |
+
Score how relevant each category/item is for the prompt, from design/invention perspective.
|
1361 |
"""
|
1362 |
prompt_lower = prompt.lower()
|
1363 |
prompt_tokens = set(re.findall(r'\b[a-zA-Z가-힣]{2,}\b', prompt_lower))
|
|
|
1381 |
if category.name_ko in prompt or category.name_en.lower() in prompt_lower:
|
1382 |
cat_score += 1
|
1383 |
|
1384 |
+
# Slight weighting by purpose
|
1385 |
if main_purpose:
|
1386 |
purpose_category_weights = {
|
1387 |
'cost_reduction': {
|
|
|
1388 |
'구조적 변화': 1.5, '화학적 변화': 1.3, '비즈니스 아이디어': 1.5,
|
1389 |
'Structural Change': 1.5, 'Chemical Change': 1.3, 'Business Ideas': 1.5,
|
|
|
1390 |
'에너지 변환 및 관리': 1.6, '데이터 및 정보 변환': 1.4, '지속가능성 및 환경 영향': 1.3,
|
1391 |
+
'Energy Conversion and Management': 1.6, 'Data and Information Transformation': 1.4,
|
1392 |
'Sustainability and Environmental Impact': 1.3
|
1393 |
},
|
1394 |
'innovation': {
|
|
|
1395 |
'센서 기능': 1.5, '표면 및 외관 변화': 1.3, '비즈니스 아이디어': 1.5,
|
1396 |
'Sensor Functions': 1.5, 'Surface and Appearance Change': 1.3, 'Business Ideas': 1.5,
|
|
|
1397 |
'사용자 인터페이스 및 상호작용': 1.6, '데이터 및 정보 변환': 1.4, '인지 및 심리적 변화': 1.3,
|
1398 |
'User Interface and Interaction': 1.6, 'Data and Information Transformation': 1.4,
|
1399 |
'Cognitive and Psychological Changes': 1.3
|
1400 |
},
|
1401 |
'risk_management': {
|
|
|
1402 |
'환경 상호작용': 1.5, '시간 관련 변화': 1.3, '비즈니스 아이디어': 1.4,
|
1403 |
'Environmental Interaction': 1.5, 'Time-Related Change': 1.3, 'Business Ideas': 1.4,
|
|
|
1404 |
'보안 및 프라이버시': 1.7, '지속가능성 및 환경 영향': 1.5, '데이터 및 정보 변환': 1.4,
|
1405 |
'Security and Privacy': 1.7, 'Sustainability and Environmental Impact': 1.5,
|
1406 |
'Data and Information Transformation': 1.4
|
1407 |
},
|
1408 |
'growth': {
|
|
|
1409 |
'크기와 형태 변화': 1.4, '비즈니스 아이디어': 1.6, '구조적 변화': 1.3,
|
1410 |
'Size and Shape Change': 1.4, 'Business Ideas': 1.6, 'Structural Change': 1.3,
|
|
|
1411 |
'사회적 상호작용 및 협업': 1.5, '데이터 및 정보 변환': 1.4, '사용자 인터페이스 및 상호작용': 1.3,
|
1412 |
'Social Interaction and Collaboration': 1.5, 'Data and Information Transformation': 1.4,
|
1413 |
'User Interface and Interaction': 1.3
|
1414 |
},
|
1415 |
'customer': {
|
|
|
1416 |
'표면 및 외관 변화': 1.5, '센서 기능': 1.4, '빛과 시각 효과': 1.3, '비즈니스 아이디어': 1.4,
|
1417 |
'Surface and Appearance Change': 1.5, 'Sensor Functions': 1.4,
|
1418 |
'Light and Visual Effects': 1.3, 'Business Ideas': 1.4,
|
|
|
1419 |
'사용자 인터페이스 및 상호작용': 1.7, '미학 및 감성 경험': 1.6, '인지 및 심리적 변화': 1.5,
|
1420 |
'사회적 상호작용 및 협업': 1.4,
|
1421 |
'User Interface and Interaction': 1.7, 'Aesthetics and Emotional Experience': 1.6,
|
|
|
1427 |
elif category.name_en in purpose_category_weights.get(main_purpose, {}):
|
1428 |
cat_score *= purpose_category_weights[main_purpose][category.name_en]
|
1429 |
|
1430 |
+
# Score by item tokens
|
1431 |
for item in category.items:
|
1432 |
item_score = cat_score
|
1433 |
item_tokens = set(re.findall(r'\b[a-zA-Z가-힣]{2,}\b', item.lower()))
|
|
|
1449 |
relevance_threshold: float = 0.2
|
1450 |
) -> list[tuple]:
|
1451 |
"""
|
1452 |
+
Generate multiple category/item combinations as 'idea' candidates.
|
|
|
1453 |
"""
|
1454 |
if relevance_scores is None:
|
1455 |
pool = [(c.name_ko, item) for c in categories for item in c.items]
|
|
|
1486 |
evaluated_combinations.sort(key=lambda x: x[3], reverse=True)
|
1487 |
return evaluated_combinations[:max_combinations]
|
1488 |
|
1489 |
+
# ────────────────────────────── Random Matrix Generator ─────────────────────────
|
1490 |
def smart_weight(cat_name, item, relevance, global_cnt, T):
|
1491 |
rare_boost = 1 / (global_cnt.get(item, 0) + 0.5)
|
1492 |
+
noise = random.random() ** (1 / T) # T bigger => noise is closer to 1
|
1493 |
relevance_weight = 1 - (T - 0.1) / 3.0
|
1494 |
return ((relevance * relevance_weight) + 0.1) * rare_boost * noise
|
1495 |
|
|
|
1504 |
T: float = 1.3,
|
1505 |
):
|
1506 |
"""
|
1507 |
+
Randomly generate a matrix of category/item combos to ensure diversity,
|
1508 |
+
helpful for design/invention idea expansion.
|
1509 |
"""
|
1510 |
if seed is None:
|
1511 |
seed = random.randrange(2 ** 32)
|
|
|
1547 |
combos.sort(key=lambda x: x[3], reverse=True)
|
1548 |
return combos[:max_combos]
|
1549 |
|
1550 |
+
# ───────────────────────────── Physical Categories ─────────────────────────
|
1551 |
PHYS_CATEGORIES: list[Category] = [
|
|
|
1552 |
Category(
|
1553 |
name_ko="센서 기능",
|
1554 |
name_en="Sensor Functions",
|
|
|
1645 |
tags=["business", "idea", "비즈니스"],
|
1646 |
items=physical_transformation_categories["비즈니스 아이디어"]
|
1647 |
),
|
|
|
|
|
1648 |
Category(
|
1649 |
name_ko="사용자 인터페이스 및 상호작용",
|
1650 |
name_en="User Interface and Interaction",
|
|
|
1694 |
items=physical_transformation_categories["미학 및 감성 경험"]
|
1695 |
)
|
1696 |
]
|
|
|
1697 |
|
1698 |
+
|
1699 |
+
# ────────────────────────── System Prompts in English/Korean ─────────────────────────
|
1700 |
+
def get_idea_system_prompt_en(selected_category: str | None = None,
|
1701 |
+
selected_frameworks: list | None = None) -> str:
|
1702 |
+
"""
|
1703 |
+
English system prompt for design/invention idea generation.
|
1704 |
+
"""
|
1705 |
+
cat_clause = (
|
1706 |
+
f'\n**Additional Note**: Please pay special attention to the selected category "{selected_category}"\n'
|
1707 |
+
) if selected_category else ""
|
1708 |
+
if not selected_frameworks:
|
1709 |
+
selected_frameworks = []
|
1710 |
+
framework_instruction = "\n\n### (Selected Additional Frameworks)\n"
|
1711 |
+
for fw in selected_frameworks:
|
1712 |
+
if fw == "sunzi":
|
1713 |
+
framework_instruction += "- Sun Tzu's 36 Stratagems\n"
|
1714 |
+
elif fw == "swot":
|
1715 |
+
framework_instruction += "- SWOT Analysis\n"
|
1716 |
+
elif fw == "porter":
|
1717 |
+
framework_instruction += "- Porter's 5 Forces\n"
|
1718 |
+
elif fw == "bcg":
|
1719 |
+
framework_instruction += "- BCG Matrix\n"
|
1720 |
+
|
1721 |
+
base_prompt = f"""
|
1722 |
+
You are a creative design/invention expert AI.
|
1723 |
+
Given the user's input, produce **"the top 5 design/invention ideas"** in detail.
|
1724 |
+
Each idea must meet these requirements:
|
1725 |
+
1) **Extensive detail** so the reader can visualize it
|
1726 |
+
2) Also provide an **image prompt** for each idea
|
1727 |
+
3) If you see insights from Kaggle datasets or web search, reference them
|
1728 |
+
4) At the end, add a **"Sources"** section with:
|
1729 |
+
- 3~5 URLs from the web search (Brave)
|
1730 |
+
- Any Kaggle dataset names/URLs
|
1731 |
+
- Other references
|
1732 |
+
5) Provide **10 additional ideas** outside the top 5, in a single line each,
|
1733 |
+
describing the core concept and novelty in one line
|
1734 |
+
|
1735 |
+
{framework_instruction}
|
1736 |
+
|
1737 |
+
## Idea Evaluation Criteria
|
1738 |
+
When selecting the top ideas, each is evaluated with the following weighting:
|
1739 |
+
1. **Innovation** (30%)
|
1740 |
+
2. **Feasibility** (25%)
|
1741 |
+
3. **Market Potential** (20%)
|
1742 |
+
4. **Social Impact** (15%)
|
1743 |
+
5. **Scalability** (10%)
|
1744 |
+
|
1745 |
+
Format your final answer in English, structured:
|
1746 |
+
1. **Topic Summary** (brief analysis approach, ~300 characters)
|
1747 |
+
2. **Top 5 Ideas Overview** (short summary and rationale, up to 400 words)
|
1748 |
+
3. **Top 5 Ideas Detailed**
|
1749 |
+
- For each idea:
|
1750 |
+
- ### Idea X: [Idea Name] (Score: x.x/10)
|
1751 |
+
- #### Core Concept
|
1752 |
+
* Provide a thorough explanation (~400 characters)
|
1753 |
+
* The specific problem it solves and importance
|
1754 |
+
* At least 3 main differences from existing solutions
|
1755 |
+
* Clearly define the unique value proposition
|
1756 |
+
- #### Detailed Design & Technical Implementation
|
1757 |
+
* Components, design specs, materials, production method
|
1758 |
+
* Dimensions, mechanics, feasibility details
|
1759 |
+
* At least 3 technical challenges + solutions
|
1760 |
+
* Potential patentable elements
|
1761 |
+
* Required key technologies/resources
|
1762 |
+
- #### Usage Scenarios & User Experience
|
1763 |
+
* At least 3 real usage scenarios with a short story
|
1764 |
+
* Define at least 2 user personas
|
1765 |
+
* Step-by-step user journey
|
1766 |
+
* Emotional connections and user feedback approach
|
1767 |
+
- #### Market Analysis & Business Model
|
1768 |
+
* TAM, SAM, SOM and growth rate estimates
|
1769 |
+
* Main customer segments + their needs
|
1770 |
+
* Compare with 5 existing competitor solutions (table) + advantage
|
1771 |
+
* Revenue model and streams
|
1772 |
+
* Market entry strategy + marketing approach
|
1773 |
+
* Scalable model analysis (business model canvas)
|
1774 |
+
- #### Implementation Roadmap & Resource Planning
|
1775 |
+
* Step-by-step plan (PoC, prototype, testing, production, etc.)
|
1776 |
+
* 6-month, 1-year, 3-year timeline + major milestones
|
1777 |
+
* Required team roles
|
1778 |
+
* Initial funding and fundraising strategy
|
1779 |
+
* Partnerships or external collaborations
|
1780 |
+
* Quality control and metrics
|
1781 |
+
- #### SWOT Analysis
|
1782 |
+
* Strengths (5 unique advantages)
|
1783 |
+
* Weaknesses (3 potential downsides + solutions)
|
1784 |
+
* Opportunities (4 external triggers for success)
|
1785 |
+
* Threats (3 external risks + mitigation)
|
1786 |
+
|
1787 |
+
4. **Additional Insights** (any selected frameworks or extra analysis)
|
1788 |
+
5. **Additional 10 Ideas** (in one line each)
|
1789 |
+
- Example: `#### Additional Idea X:\n One line description with novelty`
|
1790 |
+
|
1791 |
+
6. **Sources** (web links, Kaggle references, etc.)
|
1792 |
+
|
1793 |
+
{cat_clause}
|
1794 |
+
|
1795 |
+
No matter how long, strictly follow these instructions and output only the final completed text.
|
1796 |
+
(Do not reveal internal chain-of-thought.)
|
1797 |
+
"""
|
1798 |
+
return base_prompt.strip()
|
1799 |
+
|
1800 |
+
|
1801 |
+
def get_idea_system_prompt_kr(selected_category: str | None = None,
|
1802 |
+
selected_frameworks: list | None = None) -> str:
|
1803 |
"""
|
1804 |
+
Korean system prompt for design/invention idea generation.
|
|
|
|
|
|
|
|
|
1805 |
"""
|
1806 |
cat_clause = (
|
1807 |
f'\n**추가 지침**: 선택된 카테고리 "{selected_category}"를 특별히 우선하여 고려하세요.\n'
|
|
|
1818 |
framework_instruction += "- Porter의 5 Forces\n"
|
1819 |
elif fw == "bcg":
|
1820 |
framework_instruction += "- BCG 매트릭스\n"
|
1821 |
+
|
1822 |
base_prompt = f"""
|
1823 |
당신은 창의적 디자인/발명 전문가 AI입니다.
|
1824 |
사용자가 입력한 주제를 분석하여,
|
|
|
1826 |
각 아이디어는 다음 요구를 충족해야 합니다:
|
1827 |
1) **아주 상세하게** 설명하여, 독자가 머릿속에 이미지를 그릴 수 있을 정도로 구체적으로 서술
|
1828 |
2) **이미지 프롬프트**도 함께 제시하여, 자동 이미지 생성이 되도록 하라
|
|
|
1829 |
3) **Kaggle 데이터셋**, **웹 검색**을 활용한 통찰(또는 참조)이 있으면 반드시 결과에 언급
|
1830 |
4) 최종 출력의 마지막에 **"출처"** 섹션을 만들고,
|
1831 |
- 웹 검색(Brave)에서 참조한 URL 3~5개
|
1832 |
- Kaggle 데이터셋 이름/URL(있다면)
|
1833 |
- 그 밖의 참고 자료
|
1834 |
+
5) **추가 아이디어** 5개에 포함되지 않은 다음 순서 10개를 한 줄씩 간략하면서도 핵심 가치와 혁신점을 포함해 서술하라
|
1835 |
+
|
|
|
1836 |
{framework_instruction}
|
1837 |
|
1838 |
## 아이디어 평가 기준
|
|
|
1885 |
* 약점(Weaknesses): 잠재적 약점 3가지 이상 및 이를 극복하기 위한 구체적인 방안
|
1886 |
* 기회(Opportunities): 외부 환경(기술, 시장, 정책 등)에서 발생하는 기회 요소 4가지 이상
|
1887 |
* 위협(Threats): 성공을 방해할 수 있는 외부 요인 3가지 이상과 각각에 대한 구체적 대응책
|
1888 |
+
|
|
|
|
|
1889 |
4. **부가적 통찰** (선택된 프레임워크 분석 결과)
|
1890 |
+
5. **추가 아이디어** (TOP 5에 해당하지 않는 10가지 아이디어, 각각 한 줄로 간결하게 설명하되 해당 아이디어의 핵심 가치와 혁신점을 포함)
|
1891 |
+
- 예: `#### 부가 아이디어 X:\\n 한 줄로 자세한 한글 문구`
|
1892 |
6. **출처** (웹검색 링크, Kaggle 데이터셋 등)
|
1893 |
{cat_clause}
|
1894 |
아무리 길어도 이 요구사항을 준수하고, **오직 최종 완성된 답변**만 출력하십시오.
|
|
|
1896 |
"""
|
1897 |
return base_prompt.strip()
|
1898 |
|
|
|
1899 |
|
1900 |
+
# ────────────────────────── Web Search, Kaggle, etc. ─────────────────────────
|
1901 |
@st.cache_data(ttl=3600)
|
1902 |
def brave_search(query: str, count: int = 20):
|
1903 |
if not BRAVE_KEY:
|
1904 |
+
raise RuntimeError("⚠️ SERPHOUSE_API_KEY (Brave API Key) is missing.")
|
1905 |
headers = {
|
1906 |
"Accept": "application/json",
|
1907 |
"Accept-Encoding": "gzip",
|
|
|
1949 |
try:
|
1950 |
arts = brave_search(query, 20)
|
1951 |
if not arts:
|
1952 |
+
logging.warning("No Brave search results. Using fallback.")
|
1953 |
return mock_results(query)
|
1954 |
hdr = "# Web Search Results\nUse the information below to spark new design/invention insights.\n\n"
|
1955 |
body = "\n".join(
|
|
|
1961 |
logging.error(f"Web search process failed: {str(e)}")
|
1962 |
return mock_results(query)
|
1963 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1964 |
|
1965 |
+
# ────────────────────────── Main processing for design/invention ─────────────────────────
|
1966 |
+
|
1967 |
def idea_generator_app():
|
1968 |
+
# ---------------- Language Setting in Sidebar -----------------
|
1969 |
+
if "language" not in st.session_state:
|
1970 |
+
st.session_state["language"] = "English" # default
|
1971 |
+
|
1972 |
+
st.set_page_config(page_title="Ilúvatar: Creative Design & Invention AI", layout="wide")
|
1973 |
+
|
1974 |
+
st.title("Ilúvatar: Creative Design & Invention AI")
|
1975 |
+
st.caption("This system autonomously collects and analyzes big data to propose complex design/invention ideas.")
|
1976 |
|
1977 |
+
# Default states
|
1978 |
default_vals = {
|
1979 |
"ai_model": "gpt-4.1-mini",
|
1980 |
"messages": [],
|
|
|
1991 |
st.session_state[k] = v
|
1992 |
|
1993 |
sb = st.sidebar
|
1994 |
+
|
1995 |
+
# Language selection
|
1996 |
+
language_choice = sb.radio("Select Output Language", ["English", "Korean"], index=0 if st.session_state["language"] == "English" else 1)
|
1997 |
+
st.session_state["language"] = language_choice
|
1998 |
+
|
1999 |
st.session_state.temp = sb.slider(
|
2000 |
+
"Diversity Temperature", 0.1, 3.0, 1.3, 0.1,
|
2001 |
+
help="0.1 = Very conservative, 3.0 = Highly creative/random"
|
2002 |
)
|
2003 |
|
2004 |
sb.title("Settings")
|
|
|
2021 |
sb.error("⚠️ KAGGLE_KEY not set.")
|
2022 |
st.session_state.kaggle_enabled = False
|
2023 |
|
2024 |
+
# Example Topics
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025 |
sb.subheader("Example Topics")
|
2026 |
c1, c2, c3 = sb.columns(3)
|
2027 |
+
if c1.button("Cat Toy Design", key="ex1"):
|
2028 |
+
process_example("Cat toy design that includes interactive sensors")
|
2029 |
+
if c2.button("Jamming-resistant drone design", key="ex2"):
|
2030 |
+
process_example("A drone design resistant to jamming signals, including advanced comm protocols")
|
2031 |
+
if c3.button("Wearable UI/UX innovation", key="ex3"):
|
2032 |
+
process_example("A wearable device focusing on revolutionary UI/UX concepts")
|
2033 |
+
|
2034 |
+
# Download chat
|
2035 |
latest_ideas = next(
|
2036 |
(m["content"] for m in reversed(st.session_state.messages)
|
2037 |
if m["role"] == "assistant" and m["content"].strip()),
|
|
|
2047 |
d2.download_button("Download as HTML", md_to_html(latest_ideas, title),
|
2048 |
file_name=f"{title}.html", mime="text/html")
|
2049 |
|
2050 |
+
# Load/Save chat history
|
2051 |
up = sb.file_uploader("Load Conversation (.json)", type=["json"], key="json_uploader")
|
2052 |
if up:
|
2053 |
try:
|
2054 |
st.session_state.messages = json.load(up)
|
2055 |
+
sb.success("Conversation history loaded.")
|
2056 |
except Exception as e:
|
2057 |
sb.error(f"Failed to load: {e}")
|
2058 |
|
|
|
2064 |
mime="application/json"
|
2065 |
)
|
2066 |
|
2067 |
+
# File upload area
|
2068 |
st.subheader("File Upload (Optional)")
|
2069 |
uploaded_files = st.file_uploader(
|
2070 |
"Upload reference files (txt, csv, pdf)",
|
|
|
2097 |
if idx < len(uploaded_files) - 1:
|
2098 |
st.divider()
|
2099 |
|
2100 |
+
# Display existing messages
|
2101 |
skip_idx = st.session_state.get("_skip_dup_idx")
|
2102 |
for i, m in enumerate(st.session_state.messages):
|
2103 |
if skip_idx is not None and i == skip_idx:
|
|
|
2108 |
st.image(m["image"], caption=m.get("image_caption", ""))
|
2109 |
st.session_state["_skip_dup_idx"] = None
|
2110 |
|
2111 |
+
# Main chat input
|
2112 |
+
prompt = st.chat_input("Need a new design/invention idea? Share your context or goal here!")
|
2113 |
if prompt:
|
2114 |
process_input(prompt, uploaded_files)
|
2115 |
|
2116 |
sb.markdown("---")
|
2117 |
sb.markdown("Created by [VIDraft](https://discord.gg/openfreeai)")
|
2118 |
|
2119 |
+
|
2120 |
def process_example(topic):
|
2121 |
process_input(topic, [])
|
2122 |
|
2123 |
+
|
2124 |
def process_input(prompt: str, uploaded_files):
|
2125 |
"""
|
2126 |
+
Handle main chat input for design/invention ideas.
|
|
|
|
|
2127 |
"""
|
|
|
2128 |
if not any(m["role"] == "user" and m["content"] == prompt for m in st.session_state.messages):
|
2129 |
st.session_state.messages.append({"role": "user", "content": prompt})
|
2130 |
with st.chat_message("user"):
|
|
|
2136 |
and st.session_state.messages[i + 1]["role"] == "assistant"):
|
2137 |
return
|
2138 |
|
|
|
2139 |
with st.chat_message("assistant"):
|
2140 |
status = st.status("Preparing to generate invention ideas…")
|
2141 |
stream_placeholder = st.empty()
|
|
|
2147 |
|
2148 |
selected_cat = st.session_state.get("category_focus", None)
|
2149 |
selected_frameworks = st.session_state.get("selected_frameworks", [])
|
2150 |
+
|
2151 |
+
# Decide prompt language
|
2152 |
+
if st.session_state["language"] == "Korean":
|
2153 |
+
sys_prompt = get_idea_system_prompt_kr(
|
2154 |
+
selected_category=selected_cat,
|
2155 |
+
selected_frameworks=selected_frameworks
|
2156 |
+
)
|
2157 |
+
else:
|
2158 |
+
sys_prompt = get_idea_system_prompt_en(
|
2159 |
+
selected_category=selected_cat,
|
2160 |
+
selected_frameworks=selected_frameworks
|
2161 |
+
)
|
2162 |
|
2163 |
def category_context(sel):
|
2164 |
if sel:
|
|
|
2171 |
|
2172 |
search_content = kaggle_content = file_content = mil_content = None
|
2173 |
|
2174 |
+
# 1) Web search
|
2175 |
if use_web_search:
|
2176 |
status.update(label="Searching the web…")
|
2177 |
with st.spinner("Searching…"):
|
2178 |
search_content = do_web_search(keywords(prompt, top=5))
|
2179 |
|
2180 |
+
# 2) Kaggle
|
2181 |
if use_kaggle and check_kaggle_availability():
|
2182 |
+
status.update(label="Kaggle dataset analysis…")
|
2183 |
with st.spinner("Searching Kaggle…"):
|
2184 |
kaggle_kw = extract_kaggle_search_keywords(prompt)
|
2185 |
try:
|
2186 |
datasets = search_kaggle_datasets(kaggle_kw)
|
2187 |
except Exception as e:
|
2188 |
+
logging.warning(f"search_kaggle_datasets error ignored: {e}")
|
2189 |
datasets = []
|
2190 |
analyses = []
|
2191 |
if datasets:
|
2192 |
+
status.update(label="Downloading & analyzing datasets…")
|
2193 |
for ds in datasets:
|
2194 |
try:
|
2195 |
ana = download_and_analyze_dataset(ds["ref"])
|
2196 |
except Exception as e:
|
2197 |
+
logging.error(f"Kaggle analysis error({ds['ref']}) : {e}")
|
2198 |
+
ana = f"Error analyzing dataset: {e}"
|
2199 |
analyses.append({"meta": ds, "analysis": ana})
|
2200 |
if analyses:
|
2201 |
kaggle_content = format_kaggle_analysis_markdown_multi(analyses)
|
2202 |
|
2203 |
+
# 3) Uploaded files
|
2204 |
if has_uploaded:
|
2205 |
status.update(label="Reading uploaded files…")
|
2206 |
with st.spinner("Processing files…"):
|
2207 |
file_content = process_uploaded_files(uploaded_files)
|
2208 |
|
2209 |
+
# 4) Military dataset
|
2210 |
if is_military_query(prompt):
|
2211 |
+
status.update(label="Searching military tactics…")
|
2212 |
+
with st.spinner("Analyzing military dataset…"):
|
2213 |
mil_rows = military_search(prompt)
|
2214 |
if mil_rows:
|
2215 |
mil_content = "# Military Tactics Dataset Reference\n\n"
|
|
|
2221 |
f"**Defense Reasoning:** {row['defense_reasoning']}\n\n---\n"
|
2222 |
)
|
2223 |
|
|
|
2224 |
user_content = prompt
|
2225 |
for extra in (search_content, kaggle_content, file_content, mil_content):
|
2226 |
if extra:
|
2227 |
user_content += "\n\n" + extra
|
2228 |
|
2229 |
+
status.update(label="Analyzing categories…")
|
|
|
2230 |
decision_purpose = identify_decision_purpose(prompt)
|
2231 |
relevance_scores = compute_relevance_scores(prompt, PHYS_CATEGORIES)
|
2232 |
|
2233 |
+
status.update(label="Generating category-based ideas…")
|
2234 |
T = st.session_state.temp
|
2235 |
k_cat_range = (4, 8) if T < 1.0 else (6, 10) if T < 2.0 else (8, 12)
|
2236 |
n_item_range = (2, 4) if T < 1.0 else (3, 6) if T < 2.0 else (4, 8)
|
|
|
2245 |
T=T,
|
2246 |
)
|
2247 |
|
2248 |
+
combos_table = "| Combination | Weight | Impact | Confidence | Score |\n|------|--------|--------|--------|-------|\n"
|
2249 |
for w, imp, conf, tot, cmb in combos:
|
2250 |
combo_str = " + ".join(f"{c[0]}-{c[1]}" for c in cmb)
|
2251 |
combos_table += f"| {combo_str} | {w} | {imp} | {conf:.1f} | {tot} |\n"
|
2252 |
|
2253 |
+
purpose_info = "\n\n## Design/Invention Goal Analysis\n"
|
2254 |
if decision_purpose['purposes']:
|
2255 |
+
purpose_info += "### Main Purposes\n"
|
2256 |
for p, s in decision_purpose['purposes']:
|
2257 |
+
purpose_info += f"- **{p}** (relevance: {s})\n"
|
2258 |
if decision_purpose['constraints']:
|
2259 |
+
purpose_info += "\n### Constraints\n"
|
2260 |
for c, s in decision_purpose['constraints']:
|
2261 |
+
purpose_info += f"- **{c}** (relevance: {s})\n"
|
2262 |
|
2263 |
+
# Additional frameworks
|
2264 |
framework_contents = []
|
2265 |
for fw in selected_frameworks:
|
2266 |
if fw == "swot":
|
|
|
2277 |
)
|
2278 |
|
2279 |
if framework_contents:
|
2280 |
+
user_content += "\n\n## (Optional) Additional Framework Analysis\n\n" + "\n\n".join(framework_contents)
|
2281 |
|
2282 |
+
user_content += f"\n\n## Category Matrix Analysis{purpose_info}\n{combos_table}"
|
2283 |
|
2284 |
+
status.update(label="Generating final ideas…")
|
2285 |
|
2286 |
api_messages = [
|
2287 |
{"role": "system", "content": sys_prompt},
|
|
|
2289 |
{"role": "user", "content": user_content},
|
2290 |
]
|
2291 |
|
|
|
2292 |
@backoff.on_exception(
|
2293 |
(RemoteProtocolError, APITimeoutError, APIError), max_tries=3
|
2294 |
)
|
|
|
2302 |
stream=True
|
2303 |
)
|
2304 |
|
|
|
2305 |
try:
|
2306 |
stream = safe_stream()
|
2307 |
for chunk in stream:
|
|
|
2309 |
full_response += chunk.choices[0].delta.content
|
2310 |
stream_placeholder.markdown(full_response + "▌")
|
2311 |
except (RemoteProtocolError, APITimeoutError, APIError) as stream_err:
|
2312 |
+
logging.warning(f"Streaming failed, fallback to non-stream: {stream_err}")
|
2313 |
resp = client.chat.completions.create(
|
2314 |
model="gpt-4.1-mini",
|
2315 |
messages=api_messages,
|
|
|
2323 |
|
2324 |
status.update(label="Invention ideas created!", state="complete")
|
2325 |
|
2326 |
+
# Auto image generation
|
2327 |
img_data = img_caption = None
|
2328 |
if st.session_state.generate_image and full_response:
|
2329 |
match = re.search(r"###\s*이미지\s*프롬프트\s*\n+([^\n]+)", full_response, re.I)
|
|
|
2336 |
if img_data:
|
2337 |
st.image(img_data, caption=f"Visualized Concept – {img_caption}")
|
2338 |
|
|
|
2339 |
answer_msg = {"role": "assistant", "content": full_response}
|
2340 |
if img_data:
|
2341 |
+
answer_msg["image"] = img_data
|
2342 |
answer_msg["image_caption"] = img_caption
|
2343 |
st.session_state["_skip_dup_idx"] = len(st.session_state.messages)
|
2344 |
st.session_state.messages.append(answer_msg)
|
2345 |
|
|
|
2346 |
st.subheader("Download This Output")
|
2347 |
col_md, col_html = st.columns(2)
|
2348 |
col_md.download_button(
|
|
|
2365 |
|
2366 |
except Exception as e:
|
2367 |
logging.error("process_input error", exc_info=True)
|
2368 |
+
st.error(f"⚠️ An error occurred: {e}")
|
2369 |
st.session_state.messages.append(
|
2370 |
+
{"role": "assistant", "content": f"⚠️ Error: {e}"}
|
2371 |
)
|
2372 |
|
|
|
2373 |
def main():
|
2374 |
idea_generator_app()
|
2375 |
|