이동현 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 +114 -0
- routes/sessions.py +5 -0
- scenarios/worker-v3/engine/L3_serving.json +151 -10
- scripts/build_worker_scenario.py +78 -19
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별
|
| 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
|
| 34 |
-
# name
|
| 35 |
-
# type
|
| 36 |
-
# features
|
| 37 |
-
# apps
|
| 38 |
-
# service
|
| 39 |
-
# 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
return {"scenario_id": SCENARIO_ID, "version": "0.1.0",
|
| 108 |
-
"description": "직장인 시나리오 Intent별
|
| 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 |
|