| |
| """ |
| 페르소나 기반 가상 시드 데이터셋 생성. |
| |
| 흐름: |
| 1. 8개 페르소나 정의 (가중치·답변 분포·선호 행동 시퀀스) |
| 2. 가중치에 따라 N명 샘플링 |
| 3. 각 고객의 설문 답변·batch feature·행동 시퀀스 시뮬레이션 |
| 4. config.behavior_signals(entity→Intent) 매핑으로 양성 Intent 라벨 산출 |
| 5. scenarios/cs-myk-v3/seed_dataset.json 적재 |
| |
| 실행: |
| cd roadshow-server-v3 |
| python scripts/build_cs_dataset.py --n 500 --seed 42 |
| """ |
| from __future__ import annotations |
|
|
| import sys |
| from collections import Counter |
| from pathlib import Path |
|
|
| sys.path.insert(0, str(Path(__file__).parent.parent)) |
|
|
| from core.engines import config, get_engine |
| from scripts._dataset_common import ( |
| parse_args, build_samples, tree_action_resolver, write_dataset, print_label_stats, |
| ) |
|
|
| SCENARIO_ID = "cs-myk-v3" |
| SCENARIO_DIR = Path(__file__).parent.parent / "scenarios" / SCENARIO_ID |
|
|
| |
| |
| |
| |
| |
| |
| |
| PERSONAS = [ |
| { |
| "id": "P1", |
| "name": "데이터 헤비 + 안정 (추천 수용형)", |
| "weight": 0.15, |
| "answer_dist": { |
| "Q1": {"A": 0.3, "B": 0.5, "C": 0.2}, |
| "Q2": {"B": 0.3, "C": 0.4, "D": 0.3}, |
| "Q3": {"A": 0.2, "B": 0.5, "C": 0.3}, |
| "Q4": {"C": 0.4, "D": 0.6}, |
| "Q5": {"B": 0.5, "C": 0.5}, |
| "Q6": {"A": 0.7, "C": 0.3}, |
| "Q7": {"B": 0.4, "C": 0.6}, |
| "Q8": {"A": 0.7, "B": 0.3}, |
| "Q9": {"A": 0.8, "B": 0.2}, |
| "Q10": {"B": 0.4, "C": 0.6}, |
| "Q11": {"A": 0.6, "B": 0.4}, |
| "Q12": {"B": 0.4, "C": 0.4, "D": 0.2}, |
| "Q13": {"A": 0.5, "B": 0.4, "C": 0.1}, |
| }, |
| "action_seqs": [ |
| ["1-E", "2-E1"], ["1-E", "2-E2"], ["1-A", "2-A2"], |
| ["1-D", "2-D2"], ["1-E", "2-E3"], |
| ], |
| "expected_intents": ["INT-2130", "INT-2320", "INT-1110", "INT-2310"], |
| }, |
| { |
| "id": "P2", |
| "name": "청구 급증 + 품질 불만 (이탈 고위험)", |
| "weight": 0.12, |
| "answer_dist": { |
| "Q1": {"B": 0.3, "C": 0.4, "D": 0.3}, |
| "Q2": {"C": 0.4, "D": 0.6}, |
| "Q3": {"A": 0.2, "B": 0.5, "C": 0.3}, |
| "Q4": {"A": 0.4, "B": 0.4, "C": 0.2}, |
| "Q5": {"A": 0.6, "B": 0.4}, |
| "Q6": {"A": 0.4, "B": 0.3, "D": 0.3}, |
| "Q7": {"B": 0.5, "C": 0.5}, |
| "Q8": {"B": 0.4, "C": 0.6}, |
| "Q9": {"B": 0.5, "C": 0.5}, |
| "Q10": {"A": 0.7, "B": 0.3}, |
| "Q11": {"B": 0.5, "C": 0.5}, |
| "Q12": {"C": 0.4, "D": 0.6}, |
| "Q13": {"A": 0.7, "B": 0.3}, |
| }, |
| "action_seqs": [ |
| ["1-B", "2-B1"], ["1-C", "2-C2"], ["1-C", "2-C3"], |
| ["1-B", "2-B1", "BACK", "1-C", "2-C2"], |
| ["1-F", "2-F3"], |
| ], |
| "expected_intents": ["INT-7110", "INT-7120", "INT-7310", "INT-5410", "INT-3210"], |
| }, |
| { |
| "id": "P3", |
| "name": "시니어 저사용 (음성 헤비)", |
| "weight": 0.12, |
| "answer_dist": { |
| "Q1": {"D": 0.4, "E": 0.6}, |
| "Q2": {"C": 0.3, "D": 0.7}, |
| "Q3": {"C": 0.3, "D": 0.7}, |
| "Q4": {"A": 0.6, "B": 0.4}, |
| "Q5": {"A": 0.8, "B": 0.2}, |
| "Q6": {"B": 0.8, "D": 0.2}, |
| "Q7": {"A": 0.9, "B": 0.1}, |
| "Q8": {"A": 0.7, "B": 0.3}, |
| "Q9": {"A": 0.6, "B": 0.4}, |
| "Q10": {"A": 0.9, "B": 0.1}, |
| "Q11": {"D": 0.8, "C": 0.2}, |
| "Q12": {"D": 0.7, "C": 0.3}, |
| "Q13": {"A": 0.95, "B": 0.05}, |
| }, |
| "action_seqs": [ |
| ["1-B", "2-B2"], ["1-B", "2-B3"], ["1-F", "2-F3"], |
| ["1-A", "2-A3"], ["1-C", "2-C1"], |
| ], |
| "expected_intents": ["INT-2150", "INT-2420", "INT-1120", "INT-1320", "INT-5330"], |
| }, |
| { |
| "id": "P4", |
| "name": "출장 잦은 직장인 (로밍 의향)", |
| "weight": 0.08, |
| "answer_dist": { |
| "Q1": {"B": 0.5, "C": 0.5}, |
| "Q2": {"B": 0.3, "C": 0.4, "D": 0.3}, |
| "Q3": {"A": 0.3, "B": 0.5, "C": 0.2}, |
| "Q4": {"C": 0.3, "D": 0.7}, |
| "Q5": {"B": 0.6, "C": 0.4}, |
| "Q6": {"D": 0.7, "A": 0.3}, |
| "Q7": {"B": 0.5, "C": 0.5}, |
| "Q8": {"A": 0.5, "B": 0.5}, |
| "Q9": {"A": 0.7, "B": 0.3}, |
| "Q10": {"B": 0.5, "C": 0.5}, |
| "Q11": {"A": 0.4, "B": 0.5, "C": 0.1}, |
| "Q12": {"B": 0.4, "C": 0.4, "D": 0.2}, |
| "Q13": {"B": 0.5, "C": 0.5}, |
| }, |
| "action_seqs": [ |
| ["1-A", "2-A3"], ["1-E", "2-E2"], ["1-E", "2-E3"], |
| ["1-D", "2-D2"], |
| ], |
| "expected_intents": ["INT-2140", "INT-2540", "INT-1140", "INT-2410"], |
| }, |
| { |
| "id": "P5", |
| "name": "가족 결합 활용 가족", |
| "weight": 0.13, |
| "answer_dist": { |
| "Q1": {"C": 0.6, "D": 0.4}, |
| "Q2": {"C": 0.3, "D": 0.7}, |
| "Q3": {"B": 0.4, "C": 0.6}, |
| "Q4": {"D": 0.8, "C": 0.2}, |
| "Q5": {"B": 0.4, "C": 0.6}, |
| "Q6": {"A": 0.5, "C": 0.5}, |
| "Q7": {"B": 0.4, "C": 0.6}, |
| "Q8": {"A": 0.4, "B": 0.6}, |
| "Q9": {"A": 0.5, "B": 0.5}, |
| "Q10": {"B": 0.4, "C": 0.6}, |
| "Q11": {"A": 0.5, "B": 0.5}, |
| "Q12": {"B": 0.4, "C": 0.4, "D": 0.2}, |
| "Q13": {"A": 0.6, "B": 0.4}, |
| }, |
| "action_seqs": [ |
| ["1-E", "2-E3"], ["1-E", "2-E3", "2-E3"], |
| ["1-D", "2-D2", "BACK", "1-E", "2-E3"], |
| ["1-E", "2-E3", "BACK", "1-C", "2-C1"], |
| ], |
| "expected_intents": ["INT-6110", "INT-6120", "INT-6210", "INT-6140", "INT-1340"], |
| }, |
| { |
| "id": "P6", |
| "name": "이탈 의향 강 (해지 검토)", |
| "weight": 0.08, |
| "answer_dist": { |
| "Q1": {"B": 0.4, "C": 0.4, "D": 0.2}, |
| "Q2": {"C": 0.4, "D": 0.6}, |
| "Q3": {"A": 0.4, "B": 0.4, "C": 0.2}, |
| "Q4": {"A": 0.5, "B": 0.5}, |
| "Q5": {"A": 0.7, "B": 0.3}, |
| "Q6": {"A": 0.4, "B": 0.3, "D": 0.3}, |
| "Q7": {"B": 0.4, "C": 0.6}, |
| "Q8": {"B": 0.4, "C": 0.6}, |
| "Q9": {"B": 0.4, "C": 0.6}, |
| "Q10": {"A": 0.8, "B": 0.2}, |
| "Q11": {"C": 0.4, "D": 0.6}, |
| "Q12": {"C": 0.4, "D": 0.6}, |
| "Q13": {"A": 0.6, "B": 0.4}, |
| }, |
| "action_seqs": [ |
| ["1-C", "2-C2"], ["1-C", "2-C3"], |
| ["1-C", "2-C2", "BACK", "1-C", "2-C3"], |
| ["1-B", "2-B1", "BACK", "1-C", "2-C2", "EXIT"], |
| ], |
| "expected_intents": ["INT-7110", "INT-7120", "INT-7130", "INT-7210", "INT-7310"], |
| }, |
| { |
| "id": "P7", |
| "name": "신규 가입 콘텐츠 헤비", |
| "weight": 0.10, |
| "answer_dist": { |
| "Q1": {"A": 0.7, "B": 0.3}, |
| "Q2": {"A": 0.7, "B": 0.3}, |
| "Q3": {"B": 0.3, "C": 0.5, "D": 0.2}, |
| "Q4": {"A": 0.6, "B": 0.4}, |
| "Q5": {"A": 0.7, "B": 0.3}, |
| "Q6": {"A": 0.5, "C": 0.5}, |
| "Q7": {"B": 0.3, "C": 0.7}, |
| "Q8": {"A": 0.6, "B": 0.4}, |
| "Q9": {"A": 0.8, "B": 0.2}, |
| "Q10": {"B": 0.5, "C": 0.5}, |
| "Q11": {"A": 0.7, "B": 0.3}, |
| "Q12": {"A": 0.4, "B": 0.6}, |
| "Q13": {"A": 0.7, "B": 0.3}, |
| }, |
| "action_seqs": [ |
| ["1-D", "2-D2"], ["1-D", "2-D3"], ["1-E", "2-E1"], |
| ["1-A", "2-A1"], |
| ], |
| "expected_intents": ["INT-2310", "INT-2410", "INT-1410", "INT-2130"], |
| }, |
| { |
| "id": "P8", |
| "name": "단말 교체 임박 (약정 만료)", |
| "weight": 0.10, |
| "answer_dist": { |
| "Q1": {"B": 0.3, "C": 0.5, "D": 0.2}, |
| "Q2": {"C": 0.4, "D": 0.6}, |
| "Q3": {"B": 0.4, "C": 0.4, "D": 0.2}, |
| "Q4": {"C": 0.4, "D": 0.6}, |
| "Q5": {"B": 0.5, "C": 0.5}, |
| "Q6": {"A": 0.5, "C": 0.5}, |
| "Q7": {"B": 0.5, "C": 0.5}, |
| "Q8": {"A": 0.5, "B": 0.5}, |
| "Q9": {"A": 0.7, "B": 0.3}, |
| "Q10": {"B": 0.5, "C": 0.5}, |
| "Q11": {"A": 0.4, "B": 0.5, "C": 0.1}, |
| "Q12": {"D": 0.8, "C": 0.2}, |
| "Q13": {"A": 0.6, "B": 0.4}, |
| }, |
| "action_seqs": [ |
| ["1-E", "2-E2"], ["1-C", "2-C1"], ["1-E", "2-E2", "BACK", "1-D", "2-D1"], |
| ["1-A", "2-A1"], |
| ], |
| "expected_intents": ["INT-2410", "INT-2440", "INT-2530", "INT-1330", "INT-2110"], |
| }, |
| { |
| "id": "P9", |
| "name": "비용 민감 절감 추구", |
| "weight": 0.07, |
| "answer_dist": { |
| "Q1": {"B": 0.3, "C": 0.4, "D": 0.3}, |
| "Q2": {"B": 0.3, "C": 0.4, "D": 0.3}, |
| "Q3": {"D": 0.6, "C": 0.3, "E": 0.1}, |
| "Q4": {"A": 0.5, "B": 0.5}, |
| "Q5": {"A": 0.7, "B": 0.3}, |
| "Q6": {"B": 0.4, "A": 0.3, "C": 0.3}, |
| "Q7": {"B": 0.4, "C": 0.6}, |
| "Q8": {"B": 0.4, "C": 0.6}, |
| "Q9": {"A": 0.7, "B": 0.3}, |
| "Q10": {"A": 0.5, "B": 0.4, "C": 0.1}, |
| "Q11": {"B": 0.4, "C": 0.4, "D": 0.2}, |
| "Q12": {"C": 0.4, "D": 0.6}, |
| "Q13": {"A": 0.8, "B": 0.2}, |
| }, |
| "action_seqs": [ |
| ["1-E", "2-E1"], ["1-D", "2-D1"], ["1-B", "2-B1"], |
| ["1-D", "2-D3"], |
| ], |
| "expected_intents": ["INT-7310", "INT-2150", "INT-4110", "INT-4120", "INT-7340"], |
| }, |
| { |
| "id": "P10", |
| "name": "혜택 적극 활용 VIP", |
| "weight": 0.05, |
| "answer_dist": { |
| "Q1": {"C": 0.4, "D": 0.4, "B": 0.2}, |
| "Q2": {"D": 0.9, "C": 0.1}, |
| "Q3": {"A": 0.5, "B": 0.5}, |
| "Q4": {"D": 0.9, "C": 0.1}, |
| "Q5": {"C": 0.7, "B": 0.3}, |
| "Q6": {"A": 0.4, "C": 0.4, "D": 0.2}, |
| "Q7": {"B": 0.5, "C": 0.5}, |
| "Q8": {"A": 0.7, "B": 0.3}, |
| "Q9": {"A": 0.7, "B": 0.3}, |
| "Q10": {"C": 0.9, "B": 0.1}, |
| "Q11": {"A": 0.6, "B": 0.4}, |
| "Q12": {"B": 0.4, "C": 0.4, "D": 0.2}, |
| "Q13": {"B": 0.5, "C": 0.5}, |
| }, |
| "action_seqs": [ |
| ["1-D", "2-D2"], ["1-D", "2-D1"], ["1-D", "2-D3"], |
| ["1-E", "2-E2"], |
| ], |
| "expected_intents": ["INT-1440", "INT-4240", "INT-4220", "INT-4230", "INT-4210"], |
| }, |
| ] |
|
|
|
|
| |
| |
|
|
|
|
| def main() -> None: |
| args = parse_args() |
| behaviors = config.get_behaviors(SCENARIO_ID) |
|
|
| rows = build_samples( |
| n=args.n, seed=args.seed, personas=PERSONAS, engine=get_engine(SCENARIO_ID), |
| seq_key="action_seqs", action_resolver=tree_action_resolver(behaviors), |
| entity_intents=config.get_behavior_signals(SCENARIO_ID), |
| cust_prefix="C", |
| ) |
|
|
| write_dataset(SCENARIO_DIR / "seed_dataset.json", rows, SCENARIO_ID, len(PERSONAS), args.seed) |
|
|
| |
| persona_counts = Counter(r["persona_id"] for r in rows) |
| print(f"\n── Persona 분포 ────────────────────────────────────────") |
| for pid, cnt in sorted(persona_counts.items()): |
| name = next(p["name"] for p in PERSONAS if p["id"] == pid) |
| print(f" {pid:4s} {name:32s} : {cnt:4d} ({cnt/len(rows)*100:5.1f}%)") |
| print_label_stats(rows, top_n=20) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|