이동현 Claude Opus 4.8 commited on
Commit
1503eeb
·
1 Parent(s): 28c9a2f

[FEAT] 직장인 시나리오 데모 강화 (분석 서사·비즈니스 임팩트·멀티채널)

Browse files

- 지니뮤직·기가지니(홈IoT) 채널 추가 + intent별 액션 (build_worker_scenario)
- business_value(tag/kpi/note) 추가 — 액션 채널 기준 3-3-3(홈IoT/구독ARPU/제휴수익)
- 파생 변수 산출 근거 trace (core/feature_trace) — 산식+입력 응답 라벨 풀이
- 설문 제출 응답에 feature_trace 포함

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

core/feature_trace.py ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+ """
3
+ 파생 변수(Index/Score) 산출 근거 trace — 시나리오 무관.
4
+
5
+ L1 batch_builder 의 선언형 step formula 와 실제 입력값을 풀어,
6
+ 각 최종 파생 변수가 "어떤 입력으로 어떻게 계산됐는지"를 사람이 읽을 수 있는
7
+ 구조로 반환한다. (프론트 분석 오버레이의 변수 클릭 → 산식 팝오버용)
8
+
9
+ 반환: { 파생변수명: {
10
+ "value": float, # 최종 값
11
+ "clamp": [lo,hi] | None, # 범위 제한(있으면)
12
+ "kind": "passthrough" | "weighted_sum",
13
+ "terms": [ { # 기여 항
14
+ "ref": str, # 입력 변수명(base 또는 노출 Index명)
15
+ "ref_value": float, # 그 입력의 현재 값
16
+ "weight": float | None, # 가중치(선형 [w,0] 항일 때)
17
+ "contribution": float, # 이 항이 결과에 더한 값
18
+ }, ... ],
19
+ } }
20
+ """
21
+ from typing import Any
22
+
23
+ from core.engines import config
24
+ from core.engines.extract import survey_base
25
+ from core.engines.formula import eval_formula, _load_py
26
+
27
+
28
+ def build_feature_trace(scenario_id: str, answers: dict[str, str]) -> dict[str, dict]:
29
+ survey = config.get_survey(scenario_id)
30
+ spec = config.get_batch_builder(scenario_id)
31
+ steps = spec.get("steps", [])
32
+ if not steps:
33
+ return {}
34
+
35
+ # 0) base feature → 선택한 응답 라벨 (숫자값 대신 "22시 이후" 같은 의미 표시용)
36
+ base_answer: dict[str, str] = {}
37
+ for q in survey.get("questions", []):
38
+ code = answers.get(q["id"])
39
+ if code is None:
40
+ continue
41
+ opt = next((o for o in q.get("options", []) if o.get("code") == code), None)
42
+ if not opt:
43
+ continue
44
+ for fname in (opt.get("features") or {}):
45
+ base_answer[fname] = opt.get("label")
46
+
47
+ # 1) 모든 step을 중간값 포함해 평가 (run_batch_builder 와 동일하되 intermediate 보존)
48
+ feats: dict[str, Any] = dict(survey_base(survey, answers))
49
+ for k, v in spec.get("defaults", {}).items():
50
+ feats.setdefault(k, v)
51
+ if "pre_hook" in spec:
52
+ feats.update(_load_py(spec["pre_hook"])(feats))
53
+
54
+ inter_names: set[str] = set()
55
+ step_formula: dict[str, Any] = {}
56
+ for st in steps:
57
+ val = eval_formula(st["formula"], feats)
58
+ if "round" in st:
59
+ val = round(val, st["round"])
60
+ feats[st["name"]] = val
61
+ step_formula[st["name"]] = st["formula"]
62
+ if st.get("intermediate"):
63
+ inter_names.add(st["name"])
64
+
65
+ # 2) 중간값(_FAT 등) → 그것을 그대로 노출하는 최종 Index명 매핑 (passthrough)
66
+ inter_to_final: dict[str, str] = {}
67
+ for st in steps:
68
+ f = st["formula"]
69
+ if (not st.get("intermediate") and f.get("feat") in inter_names
70
+ and f.get("linear") == [1, 0] and "div" not in f and "mul" not in f):
71
+ inter_to_final[f["feat"]] = st["name"]
72
+
73
+ def _terms(formula: dict) -> list[dict]:
74
+ if "terms" in formula:
75
+ return formula["terms"]
76
+ return [formula] # 단일 feat 노드
77
+
78
+ def _weight(term: dict):
79
+ lin = term.get("linear")
80
+ if lin and lin[1] == 0 and "div" not in term and "mul" not in term:
81
+ return lin[0]
82
+ return None
83
+
84
+ trace: dict[str, dict] = {}
85
+ for st in steps:
86
+ if st.get("intermediate"):
87
+ continue
88
+ name = st["name"]
89
+ f = st["formula"]
90
+ # passthrough Index 는 중간값 산식을 인라인해 base 입력까지 노출
91
+ eff = f
92
+ kind = "weighted_sum"
93
+ if f.get("feat") in inter_names and f.get("linear") == [1, 0]:
94
+ eff = step_formula[f["feat"]]
95
+ kind = "passthrough"
96
+ clamp = eff["clamp"] if isinstance(eff, dict) and "clamp" in eff else None
97
+
98
+ terms = []
99
+ for t in _terms(eff):
100
+ ref = t.get("feat")
101
+ terms.append({
102
+ "ref": inter_to_final.get(ref, ref),
103
+ "ref_value": round(float(feats.get(ref, 0.0)), 2),
104
+ "ref_answer": base_answer.get(ref), # base 입력이면 선택 응답 라벨 (없으면 None)
105
+ "weight": _weight(t),
106
+ "contribution": round(eval_formula(t, feats), 2),
107
+ })
108
+ trace[name] = {
109
+ "value": round(float(feats[name]), 2),
110
+ "clamp": clamp,
111
+ "kind": kind,
112
+ "terms": terms,
113
+ }
114
+ return trace
routes/sessions.py CHANGED
@@ -122,10 +122,15 @@ async def submit_survey(session_id: str, submission: SurveySubmission) -> dict[s
122
  _explain.attach_reasoning(_eng, _combined, top_items)
123
  all_probabilities = to_probability_dict(intent_scores, scenario_id=scenario_id)
124
 
 
 
 
 
125
  return {
126
  "session_id": session_id,
127
  "stage": "initial",
128
  "batch_features": batch_features,
 
129
  "top_n": top_items,
130
  "others": others,
131
  "all_probabilities": all_probabilities,
 
122
  _explain.attach_reasoning(_eng, _combined, top_items)
123
  all_probabilities = to_probability_dict(intent_scores, scenario_id=scenario_id)
124
 
125
+ # 파생 변수 산출 근거(분석 오버레이의 변수 클릭 → 산식 표시)
126
+ from core.feature_trace import build_feature_trace
127
+ feature_trace = build_feature_trace(scenario_id, submission.answers)
128
+
129
  return {
130
  "session_id": session_id,
131
  "stage": "initial",
132
  "batch_features": batch_features,
133
+ "feature_trace": feature_trace,
134
  "top_n": top_items,
135
  "others": others,
136
  "all_probabilities": all_probabilities,
scenarios/worker-v3/engine/L3_serving.json CHANGED
@@ -2,51 +2,192 @@
2
  "context_library": {
3
  "scenario_id": "worker-v3",
4
  "version": "0.1.0",
5
- "description": "직장인 시나리오 Intent별 MyKT 앱 Push 활용 예시",
6
  "channels": [
7
  {
8
  "id": "push",
9
  "name": "앱 Push",
10
  "icon": "📱",
 
11
  "characteristic": "고객 상태에 맞는 추천 서비스를 MyKT 앱 Push 메시지로 제공 — 번아웃 완화·일상 회복 지원"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  }
13
  ],
14
  "actions": {
15
  "INT-W110": {
16
  "push": "오늘 하루도 수고하셨습니다. MyKT 지니뮤직 ASMR 플레이리스트로 조용한 휴식 시간을 가져보세요.",
17
- "service": "지니뮤직 힐링·ASMR 콘텐츠"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  },
19
  "INT-W120": {
20
  "push": "지친 저녁, 직접 준비하지 않아도 괜찮아요. MyKT 멤버십 배달 할인 쿠폰이 도착했습니다.",
21
- "service": "배달 제휴 할인 혜택"
 
 
 
 
 
 
 
 
 
 
 
 
22
  },
23
  "INT-W130": {
24
  "push": "늦은 시간까지 깨어 계시네요. 지니뮤직 수면 유도 콘텐츠로 편안한 밤을 준비해보세요.",
25
- "service": "수면·명상 콘텐츠"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  },
27
  "INT-W140": {
28
  "push": "최근 생활 리듬이 불규칙해 보입니다. 운동·건강 제휴 혜택으로 가벼운 산책부터 시작해보세요.",
29
- "service": "건강·운동 제휴 혜택"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  },
31
  "INT-W210": {
32
  "push": "오늘 사용할 수 있는 배달·쇼핑 멤버십 할인 혜택이 있습니다. 작은 보상으로 하루를 마무리해보세요.",
33
- "service": "배달·쇼핑 멤버십 할인"
 
 
 
 
 
 
 
 
 
34
  },
35
  "INT-W220": {
36
  "push": "이번 주말 잠시 떠나보는 건 어떨까요? MyKT 전용 숙박 할인 혜택을 확인해보세요.",
37
- "service": "여행·숙박 프로모션"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  },
39
  "INT-W230": {
40
  "push": "운동을 시작하기 좋은 타이밍입니다. 헬스·러닝 제휴 쿠폰이 준비되어 있습니다.",
41
- "service": "헬스·러닝 제휴 쿠폰"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  },
43
  "INT-W240": {
44
  "push": "마음을 쉬게 해줄 시간이 필요해 보입니다. 지니뮤직 명상 콘텐츠를 추천드립니다.",
45
- "service": "지니뮤직 명상·힐링 콘텐츠"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  },
47
  "INT-W250": {
48
  "push": "오랜만에 지인과 연락해보는 건 어떨까요? 카페 멤버십 할인 혜택을 활용해보세요.",
49
- "service": "카페·문화 멤버십 혜택"
 
 
 
 
 
 
 
 
 
 
 
 
50
  }
51
  }
52
  }
 
2
  "context_library": {
3
  "scenario_id": "worker-v3",
4
  "version": "0.1.0",
5
+ "description": "직장인 시나리오 Intent별 멀티채널 활용 예시 (Push·지니뮤직·기가지니) + 비즈니스 임팩트",
6
  "channels": [
7
  {
8
  "id": "push",
9
  "name": "앱 Push",
10
  "icon": "📱",
11
+ "kind": "phone-push",
12
  "characteristic": "고객 상태에 맞는 추천 서비스를 MyKT 앱 Push 메시지로 제공 — 번아웃 완화·일상 회복 지원"
13
+ },
14
+ {
15
+ "id": "genie_music",
16
+ "name": "지니뮤직",
17
+ "icon": "🎵",
18
+ "kind": "music-card",
19
+ "characteristic": "평소 청취 이력·시간대 취향을 바탕으로 지금 상태에 맞는 플레이리스트를 큐레이션"
20
+ },
21
+ {
22
+ "id": "gigagenie",
23
+ "name": "기가지니 · 홈IoT",
24
+ "icon": "🏠",
25
+ "kind": "device-voice",
26
+ "characteristic": "음성 한마디로 집안 환경(조명·커튼·도어락·가전)을 상태에 맞게 자동 세팅"
27
  }
28
  ],
29
  "actions": {
30
  "INT-W110": {
31
  "push": "오늘 하루도 수고하셨습니다. MyKT 지니뮤직 ASMR 플레이리스트로 조용한 휴식 시간을 가져보세요.",
32
+ "service": "지니뮤직 힐링·ASMR 콘텐츠",
33
+ "gigagenie": {
34
+ "command": "동굴 모드 켜줘",
35
+ "devices": [
36
+ "🪟 커튼 닫기",
37
+ "💡 조명 어둡게",
38
+ "🔒 도어락 이중잠금"
39
+ ],
40
+ "desc": "외부를 닫고 아늑하게 — 혼자 있고 싶은 상태를 집 환경으로 받쳐줍니다."
41
+ },
42
+ "business_value": {
43
+ "tag": "홈IoT 활용",
44
+ "kpi": "기가지니 사용 ↑",
45
+ "note": "동굴 모드 자동 세팅으로 기가지니 활용"
46
+ }
47
  },
48
  "INT-W120": {
49
  "push": "지친 저녁, 직접 준비하지 않아도 괜찮아요. MyKT 멤버십 배달 할인 쿠폰이 도착했습니다.",
50
+ "service": "배달 제휴 할인 혜택",
51
+ "gigagenie": {
52
+ "command": "청소 좀 해줘",
53
+ "devices": [
54
+ "🤖 로봇청소기 가동"
55
+ ],
56
+ "desc": "기력이 없을 때 집안일을 대신 — 최소한의 부담으로 정돈된 환경 유지."
57
+ },
58
+ "business_value": {
59
+ "tag": "제휴수익",
60
+ "kpi": "멤버십 ↑",
61
+ "note": "배달·생활 제휴 전환"
62
+ }
63
  },
64
  "INT-W130": {
65
  "push": "늦은 시간까지 깨어 계시네요. 지니뮤직 수면 유도 콘텐츠로 편안한 밤을 준비해보세요.",
66
+ "service": "수면·명상 콘텐츠",
67
+ "genie_music": {
68
+ "playlist": "수면 플레이리스트",
69
+ "desc": "새벽 시간대 청취 이력을 바탕으로 잠들기 좋은 사운드를 큐레이션."
70
+ },
71
+ "gigagenie": {
72
+ "command": "취침 환경으로 바꿔줘",
73
+ "devices": [
74
+ "📺 올레tv OFF",
75
+ "🎵 지니뮤직 수면사운드",
76
+ "🛏️ 모션베드 콰이어트 슬립"
77
+ ],
78
+ "desc": "TV에서 수면 음악으로 자연스럽게 핸드오프 — 화면을 끄고 숙면 모드로."
79
+ },
80
+ "business_value": {
81
+ "tag": "구독 ARPU",
82
+ "kpi": "지니뮤직 ↑",
83
+ "note": "수면 콘텐츠 구독 전환"
84
+ }
85
  },
86
  "INT-W140": {
87
  "push": "최근 생활 리듬이 불규칙해 보입니다. 운동·건강 제휴 혜택으로 가벼운 산책부터 시작해보세요.",
88
+ "service": "건강·운동 제휴 혜택",
89
+ "gigagenie": {
90
+ "command": "아침 루틴 규칙적으로",
91
+ "devices": [
92
+ "⏰ 7시 알람",
93
+ "🪟 7시 블라인드 열기",
94
+ "💡 조명 점등"
95
+ ],
96
+ "desc": "아침을 일정하게 — 불규칙해진 생활 리듬을 환경으로 다잡습니다."
97
+ },
98
+ "business_value": {
99
+ "tag": "홈IoT 활용",
100
+ "kpi": "기가지니 사용 ↑",
101
+ "note": "아침 루틴 자동화로 기가지니를 매일 활용"
102
+ }
103
  },
104
  "INT-W210": {
105
  "push": "오늘 사용할 수 있는 배달·쇼핑 멤버십 할인 혜택이 있습니다. 작은 보상으로 하루를 마무리해보세요.",
106
+ "service": "배달·쇼핑 멤버십 할인",
107
+ "genie_music": {
108
+ "playlist": "신나는 플리",
109
+ "desc": "기분 전환용 업비트 — 도파민을 끌어올리는 플레이리스트."
110
+ },
111
+ "business_value": {
112
+ "tag": "구독 ARPU",
113
+ "kpi": "지니뮤직 ↑",
114
+ "note": "기분 전환 플레이리스트로 지니뮤직 활용"
115
+ }
116
  },
117
  "INT-W220": {
118
  "push": "이번 주말 잠시 떠나보는 건 어떨까요? MyKT 전용 숙박 할인 혜택을 확인해보세요.",
119
+ "service": "여행·숙박 프로모션",
120
+ "gigagenie": {
121
+ "command": "외출모드로 바꿔줘",
122
+ "devices": [
123
+ "💡 조명 OFF",
124
+ "🔌 플러그 OFF",
125
+ "🔒 도어락 잠금"
126
+ ],
127
+ "desc": "즉흥 나들이를 가볍게 — 길안내·날씨와 함께 외출 환경을 한 번에 정리."
128
+ },
129
+ "business_value": {
130
+ "tag": "제휴수익",
131
+ "kpi": "여행·숙박 ↑",
132
+ "note": "여행·숙박 제휴 프로모션 전환"
133
+ }
134
  },
135
  "INT-W230": {
136
  "push": "운동을 시작하기 좋은 타이밍입니다. 헬스·러닝 제휴 쿠폰이 준비되어 있습니다.",
137
+ "service": "헬스·러닝 제휴 쿠폰",
138
+ "genie_music": {
139
+ "playlist": "운동할 때 듣는 음악",
140
+ "desc": "러닝·홈트 템포에 맞춘 운동 플레이리스트."
141
+ },
142
+ "gigagenie": {
143
+ "command": "운동 타이머 맞춰줘",
144
+ "devices": [
145
+ "⏱️ 운동 타이머 설정"
146
+ ],
147
+ "desc": "가볍게 몸을 움직이는 루틴을 지원."
148
+ },
149
+ "business_value": {
150
+ "tag": "제휴수익",
151
+ "kpi": "헬스 제휴 ↑",
152
+ "note": "헬스·러닝 제휴 전환"
153
+ }
154
  },
155
  "INT-W240": {
156
  "push": "마음을 쉬게 해줄 시간이 필요해 보입니다. 지니뮤직 명상 콘텐츠를 추천드립니다.",
157
+ "service": "지니뮤직 명상·힐링 콘텐츠",
158
+ "genie_music": {
159
+ "playlist": "힐링 음악",
160
+ "desc": "마음을 가라앉히는 명상·힐링 사운드."
161
+ },
162
+ "gigagenie": {
163
+ "command": "힐링 모드로 바꿔줘",
164
+ "devices": [
165
+ "🌫️ 디퓨저 ON",
166
+ "💡 조명 따뜻하게·낮춤"
167
+ ],
168
+ "desc": "감정을 쉬게 하는 분위기로 — 향과 빛으로 안정감을 더합니다."
169
+ },
170
+ "business_value": {
171
+ "tag": "구독 ARPU",
172
+ "kpi": "지니뮤직 ↑",
173
+ "note": "힐링·명상 구독 전환"
174
+ }
175
  },
176
  "INT-W250": {
177
  "push": "오랜만에 지인과 연락해보는 건 어떨까요? 카페 멤버십 할인 혜택을 활용해보세요.",
178
+ "service": "카페·문화 멤버십 혜택",
179
+ "gigagenie": {
180
+ "command": "안부 챙기기 일정 추가해줘",
181
+ "devices": [
182
+ "📅 캘린더: 이번 주말 친구 만나기"
183
+ ],
184
+ "desc": "관계 회복의 첫걸음을 일정으로 — 일상으로 돌아오는 작은 계기."
185
+ },
186
+ "business_value": {
187
+ "tag": "홈IoT 활용",
188
+ "kpi": "기가지니 사용 ↑",
189
+ "note": "안부 일정 등록으로 기가지니 활용"
190
+ }
191
  }
192
  }
193
  }
scripts/build_worker_scenario.py CHANGED
@@ -30,50 +30,94 @@ L1 = {
30
  }
31
 
32
  # L3(=L2 수준) Intent:
33
- # key : 코드 (앞 1자리가 L1)
34
- # name : Intent 명
35
- # type : Rule | Model
36
- # features : 추론 핵심 batch feature (Index/Score)
37
- # apps : 이 Intent로 직접 신호를 주는 앱 entity
38
- # service : 추천 서비스
39
- # push : MyKT 앱 Push 문구
 
 
 
40
  TAX = [
41
  dict(key="110", name="동굴속 휴식", type="Model",
42
  features=["Burnout Deep Score", "Isolation Tendency Index", "Sleep Disturbance Index"],
43
  apps=[], service="지니뮤직 힐링·ASMR 콘텐츠",
44
- push="오늘 하루도 수고하셨습니다. MyKT 지니뮤직 ASMR 플레이리스트로 조용한 휴식 시간을 가져보세요."),
 
 
 
 
45
  dict(key="120", name="에너지 절약 모드", type="Model",
46
  features=["Fatigue Load Index", "Burnout Deep Score"],
47
  apps=["delivery"], service="배달 제휴 할인 혜택",
48
- push="지친 저녁, 직접 준비하지 않아도 괜찮아요. MyKT 멤버십 배달 할인 쿠폰이 도착했습니다."),
 
 
 
 
49
  dict(key="130", name="늦은 밤 디지털 활동", type="Rule",
50
  features=["Sleep Disturbance Index", "Digital Escape Score"],
51
  apps=["sns", "ott"], service="수면·명상 콘텐츠",
52
- push="늦은 시간까지 깨어 계시네요. 지니뮤직 수면 유도 콘텐츠로 편안한 밤을 준비해보세요."),
 
 
 
 
 
 
53
  dict(key="140", name="생활 리듬 불규칙", type="Model",
54
  features=["Fatigue Load Index", "Isolation Tendency Index", "weekend_out"],
55
  apps=["sns", "ott"], service="건강·운동 제휴 혜택",
56
- push="최근 생활 리듬이 불규칙해 보입니다. 운동·건강 제휴 혜택으로 가벼운 산책부터 시작해보세요."),
 
 
 
 
57
  dict(key="210", name="즉각 스트레스 해소", type="Rule",
58
  features=["Burnout Deep Score", "Fatigue Load Index"],
59
  apps=["delivery", "shopping"], service="배달·쇼핑 멤버십 할인",
60
- push="오늘 사용할 수 있는 배달·쇼핑 멤버십 할인 혜택이 있습니다. 작은 보상으로 하루를 마무리해보세요."),
 
 
 
61
  dict(key="220", name="환경 전환 욕구", type="Rule",
62
  features=["Recovery Motivation Score", "Isolation Tendency Index"],
63
  apps=["travel"], service="여행·숙박 프로모션",
64
- push="이번 주말 잠시 떠나보는 건 어떨까요? MyKT 전용 숙박 할인 혜택을 확인해보세요."),
 
 
 
 
65
  dict(key="230", name="신체 회복 시도", type="Rule",
66
  features=["Recovery Motivation Score", "Fatigue Load Index"],
67
  apps=["exercise"], service="헬스·러닝 제휴 쿠폰",
68
- push="운동을 시작하기 좋은 타이밍입니다. 헬스·러닝 제휴 쿠폰이 준비되어 있습니다."),
 
 
 
 
 
 
69
  dict(key="240", name="심리·감정 회복", type="Rule",
70
  features=["Recovery Motivation Score", "Isolation Tendency Index"],
71
  apps=["mental_recovery", "music"], service="지니뮤직 명상·힐링 콘텐츠",
72
- push="마음을 쉬게 해줄 시간이 필요해 보입니다. 지니뮤직 명상 콘텐츠를 추천드립니다."),
 
 
 
 
 
 
73
  dict(key="250", name="일상 회복", type="Rule",
74
  features=["Recovery Motivation Score", "Isolation Tendency Index"],
75
  apps=["messenger", "reading", "exercise"], service="카페·문화 멤버십 혜택",
76
- push="오랜만에 지인과 연락해보는 건 어떨까요? 카페 멤버십 할인 혜택을 활용해보세요."),
 
 
 
 
77
  ]
78
 
79
 
@@ -93,9 +137,17 @@ def build_intents():
93
  "intents": intents}
94
 
95
 
 
 
 
 
96
  CHANNELS = [
97
- {"id": "push", "name": "앱 Push", "icon": "📱",
98
  "characteristic": "고객 상태에 맞는 추천 서비스를 MyKT 앱 Push 메시지로 제공 — 번아웃 완화·일상 회복 지원"},
 
 
 
 
99
  ]
100
 
101
 
@@ -103,9 +155,16 @@ def build_actions():
103
  actions = {}
104
  for t in TAX:
105
  iid = f"INT-W{t['key']}"
106
- actions[iid] = {"push": t["push"], "service": t["service"]}
 
 
 
 
 
 
 
107
  return {"scenario_id": SCENARIO_ID, "version": "0.1.0",
108
- "description": "직장인 시나리오 Intent별 MyKT 앱 Push 활용 예시",
109
  "channels": CHANNELS, "actions": actions}
110
 
111
 
 
30
  }
31
 
32
  # L3(=L2 수준) Intent:
33
+ # key : 코드 (앞 1자리가 L1)
34
+ # name : Intent 명
35
+ # type : Rule | Model
36
+ # features : 추론 핵심 batch feature (Index/Score)
37
+ # apps : 이 Intent로 직접 신호를 주는 앱 entity
38
+ # service : 추천 서비스
39
+ # push : MyKT 앱 Push 문구
40
+ # gmusic : 🎵 지니뮤직 채널 — {playlist, desc} (없으면 생략)
41
+ # genie : 🏠 기가지니·홈IoT 채널 — {command, devices[], desc} (없으면 생략)
42
+ # biz : 💼 비즈니스 임팩트 — {tag, kpi, note}
43
  TAX = [
44
  dict(key="110", name="동굴속 휴식", type="Model",
45
  features=["Burnout Deep Score", "Isolation Tendency Index", "Sleep Disturbance Index"],
46
  apps=[], service="지니뮤직 힐링·ASMR 콘텐츠",
47
+ push="오늘 하루도 수고하셨습니다. MyKT 지니뮤직 ASMR 플레이리스트로 조용한 휴식 시간을 가져보세요.",
48
+ genie=dict(command="동굴 모드 켜줘",
49
+ devices=["🪟 커튼 닫기", "💡 조명 어둡게", "🔒 도어락 이중잠금"],
50
+ desc="외부를 닫고 아늑하게 — 혼자 있고 싶은 상태를 집 환경으로 받쳐줍니다."),
51
+ biz=dict(tag="홈IoT 활용", kpi="기가지니 사용 ↑", note="동굴 모드 자동 세팅으로 기가지니 활용")),
52
  dict(key="120", name="에너지 절약 모드", type="Model",
53
  features=["Fatigue Load Index", "Burnout Deep Score"],
54
  apps=["delivery"], service="배달 제휴 할인 혜택",
55
+ push="지친 저녁, 직접 준비하지 않아도 괜찮아요. MyKT 멤버십 배달 할인 쿠폰이 도착했습니다.",
56
+ genie=dict(command="청소 좀 해줘",
57
+ devices=["🤖 로봇청소기 가동"],
58
+ desc="기력이 없을 때 집안일을 대신 — 최소한의 부담으로 정돈된 환경 유지."),
59
+ biz=dict(tag="제휴수익", kpi="멤버십 ↑", note="배달·생활 제휴 전환")),
60
  dict(key="130", name="늦은 밤 디지털 활동", type="Rule",
61
  features=["Sleep Disturbance Index", "Digital Escape Score"],
62
  apps=["sns", "ott"], service="수면·명상 콘텐츠",
63
+ push="늦은 시간까지 깨어 계시네요. 지니뮤직 수면 유도 콘텐츠로 편안한 밤을 준비해보세요.",
64
+ gmusic=dict(playlist="수면 플레이리스트",
65
+ desc="새벽 시간대 청취 이력을 바탕으로 잠들기 좋은 사운드를 큐레이션."),
66
+ genie=dict(command="취침 환경으로 바꿔줘",
67
+ devices=["📺 올레tv OFF", "🎵 지니뮤직 수면사운드", "🛏️ 모션베드 콰이어트 슬립"],
68
+ desc="TV에서 수면 음악으로 자연스럽게 핸드오프 — 화면을 끄고 숙면 모드로."),
69
+ biz=dict(tag="구독 ARPU", kpi="지니뮤직 ↑", note="수면 콘텐츠 구독 전환")),
70
  dict(key="140", name="생활 리듬 불규칙", type="Model",
71
  features=["Fatigue Load Index", "Isolation Tendency Index", "weekend_out"],
72
  apps=["sns", "ott"], service="건강·운동 제휴 혜택",
73
+ push="최근 생활 리듬이 불규칙해 보입니다. 운동·건강 제휴 혜택으로 가벼운 산책부터 시작해보세요.",
74
+ genie=dict(command="아침 루틴 규칙적으로",
75
+ devices=["⏰ 7시 알람", "🪟 7시 블라인드 열기", "💡 조명 점등"],
76
+ desc="아침을 일정하게 — 불규칙해진 생활 리듬을 환경으로 다잡습니다."),
77
+ biz=dict(tag="홈IoT 활용", kpi="기가지니 사용 ↑", note="아침 루틴 자동화로 기가지니를 매일 활용")),
78
  dict(key="210", name="즉각 스트레스 해소", type="Rule",
79
  features=["Burnout Deep Score", "Fatigue Load Index"],
80
  apps=["delivery", "shopping"], service="배달·쇼핑 멤버십 할인",
81
+ push="오늘 사용할 수 있는 배달·쇼핑 멤버십 할인 혜택이 있습니다. 작은 보상으로 하루를 마무리해보세요.",
82
+ gmusic=dict(playlist="신나는 플리",
83
+ desc="기분 전환용 업비트 — 도파민을 끌어올리는 플레이리스트."),
84
+ biz=dict(tag="구독 ARPU", kpi="지니뮤직 ↑", note="기분 전환 플레이리스트로 지니뮤직 활용")),
85
  dict(key="220", name="환경 전환 욕구", type="Rule",
86
  features=["Recovery Motivation Score", "Isolation Tendency Index"],
87
  apps=["travel"], service="여행·숙박 프로모션",
88
+ push="이번 주말 잠시 떠나보는 건 어떨까요? MyKT 전용 숙박 할인 혜택을 확인해보세요.",
89
+ genie=dict(command="외출모드로 바꿔줘",
90
+ devices=["💡 조명 OFF", "🔌 플러그 OFF", "🔒 도어락 잠금"],
91
+ desc="즉흥 나들이를 가볍게 — 길안내·날씨와 함께 외출 환경을 한 번에 정리."),
92
+ biz=dict(tag="제휴수익", kpi="여행·숙박 ↑", note="여행·숙박 제휴 프로모션 전환")),
93
  dict(key="230", name="신체 회복 시도", type="Rule",
94
  features=["Recovery Motivation Score", "Fatigue Load Index"],
95
  apps=["exercise"], service="헬스·러닝 제휴 쿠폰",
96
+ push="운동을 시작하기 좋은 타이밍입니다. 헬스·러닝 제휴 쿠폰이 준비되어 있습니다.",
97
+ gmusic=dict(playlist="운동할 때 듣는 음악",
98
+ desc="러닝·홈트 템포에 맞춘 운동 플레이리스트."),
99
+ genie=dict(command="운동 타이머 맞춰줘",
100
+ devices=["⏱️ 운동 타이머 설정"],
101
+ desc="가볍게 몸을 움직이는 루틴을 지원."),
102
+ biz=dict(tag="제휴수익", kpi="헬스 제휴 ↑", note="헬스·러닝 제휴 전환")),
103
  dict(key="240", name="심리·감정 회복", type="Rule",
104
  features=["Recovery Motivation Score", "Isolation Tendency Index"],
105
  apps=["mental_recovery", "music"], service="지니뮤직 명상·힐링 콘텐츠",
106
+ push="마음을 쉬게 해줄 시간이 필요해 보입니다. 지니뮤직 명상 콘텐츠를 추천드립니다.",
107
+ gmusic=dict(playlist="힐링 음악",
108
+ desc="마음을 가라앉히는 명상·힐링 사운드."),
109
+ genie=dict(command="힐링 모드로 바꿔줘",
110
+ devices=["🌫️ 디퓨저 ON", "💡 조명 따뜻하게·낮춤"],
111
+ desc="감정을 쉬게 하는 분위기로 — 향과 빛으로 안정감을 더합니다."),
112
+ biz=dict(tag="구독 ARPU", kpi="지니뮤직 ↑", note="힐링·명상 구독 전환")),
113
  dict(key="250", name="일상 회복", type="Rule",
114
  features=["Recovery Motivation Score", "Isolation Tendency Index"],
115
  apps=["messenger", "reading", "exercise"], service="카페·문화 멤버십 혜택",
116
+ push="오랜만에 지인과 연락해보는 건 어떨까요? 카페 멤버십 할인 혜택을 활용해보세요.",
117
+ genie=dict(command="안부 챙기기 일정 추가해줘",
118
+ devices=["📅 캘린더: 이번 주말 친구 만나기"],
119
+ desc="관계 회복의 첫걸음을 일정으로 — 일상으로 돌아오는 작은 계기."),
120
+ biz=dict(tag="홈IoT 활용", kpi="기가지니 사용 ↑", note="안부 일정 등록으로 기가지니 활용")),
121
  ]
122
 
123
 
 
137
  "intents": intents}
138
 
139
 
140
+ # 채널 — kind: 프론트의 범용 렌더러가 채널 종류별 목업을 결정 (시나리오 특화 코드 지양)
141
+ # phone-push : 휴대폰 락스크린 푸시 목업
142
+ # music-card : 지니뮤직 플레이리스트 카드
143
+ # device-voice: 기가지니 음성 명령 + 홈IoT 기기 동작 칩
144
  CHANNELS = [
145
+ {"id": "push", "name": "앱 Push", "icon": "📱", "kind": "phone-push",
146
  "characteristic": "고객 상태에 맞는 추천 서비스를 MyKT 앱 Push 메시지로 제공 — 번아웃 완화·일상 회복 지원"},
147
+ {"id": "genie_music", "name": "지니뮤직", "icon": "🎵", "kind": "music-card",
148
+ "characteristic": "평소 청취 이력·시간대 취향을 바탕으로 지금 상태에 맞는 플레이리스트를 큐레이션"},
149
+ {"id": "gigagenie", "name": "기가지니 · 홈IoT", "icon": "🏠", "kind": "device-voice",
150
+ "characteristic": "음성 한마디로 집안 환경(조명·커튼·도어락·가전)을 상태에 맞게 자동 세팅"},
151
  ]
152
 
153
 
 
155
  actions = {}
156
  for t in TAX:
157
  iid = f"INT-W{t['key']}"
158
+ act = {"push": t["push"], "service": t["service"]}
159
+ if t.get("gmusic"):
160
+ act["genie_music"] = t["gmusic"]
161
+ if t.get("genie"):
162
+ act["gigagenie"] = t["genie"]
163
+ if t.get("biz"):
164
+ act["business_value"] = t["biz"]
165
+ actions[iid] = act
166
  return {"scenario_id": SCENARIO_ID, "version": "0.1.0",
167
+ "description": "직장인 시나리오 Intent별 멀티채널 활용 예시 (Push·지니뮤직·기가지니) + 비즈니스 임팩트",
168
  "channels": CHANNELS, "actions": actions}
169
 
170