Jev 내담자 평가·표현 v2와 속마음 공개
상담자 발화 판정(A)·감정(B)·표현(C) 20문항 질문 세트, 감쇠 없는 이번 턴 반응과 비대칭 기분 전이, 개방도 게이트로 생성 지시를 만들고 ccd.coping_strategy 전달 누락을 고친다. trace v2와 고정 문구 속마음 요약(migration 24, AI 경로 차단 RLS)을 같은 트랜잭션에 저장하고 피드백 정책이 켜진 경우에만 done·TurnResponse·음성 reply·리뷰로 노출한다. 회기 화면 속마음 보기 토글, 리뷰 접힘 블록, 관리자 감정 관측 v2 표시를 추가한다.
This commit is contained in:
parent
36cb847e38
commit
29c406d89f
39 changed files with 5726 additions and 343 deletions
156
apps/web/src/pages/admin-affect/affectLabels.ts
Normal file
156
apps/web/src/pages/admin-affect/affectLabels.ts
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
// jev-affect-v2 trace(§8.1)의 코드값을 관리자 화면 한글 라벨로 옮긴다.
|
||||
// 모르는 코드는 원문 코드를 그대로 보여 주지 않고 "알 수 없는 값"으로 대체한다.
|
||||
|
||||
const UNKNOWN_LABEL = "알 수 없는 값";
|
||||
|
||||
const APPRAISAL_ITEM_LABELS: Record<string, string> = {
|
||||
a_understood: "이해받았다고 느낌",
|
||||
a_judged: "평가·비난받는 느낌",
|
||||
a_autonomy: "선택을 강요받는 느낌",
|
||||
a_coping: "제안의 감당 가능성",
|
||||
a_directionless: "방향을 잃은 느낌",
|
||||
a_sore_spot: "민감 지점 자극",
|
||||
a_fact_conflict: "고정 사실과의 충돌",
|
||||
a_move: "상담자 발화 유형",
|
||||
};
|
||||
|
||||
export function appraisalItemLabel(key: string): string {
|
||||
return APPRAISAL_ITEM_LABELS[key] ?? UNKNOWN_LABEL;
|
||||
}
|
||||
|
||||
const A_COPING_LABELS: Record<string, string> = {
|
||||
nothing_asked: "요구 없음",
|
||||
manageable: "감당 가능",
|
||||
stretch: "부담스러움",
|
||||
overwhelming: "벅참",
|
||||
};
|
||||
const A_COPING_ORDER = ["nothing_asked", "manageable", "stretch", "overwhelming"];
|
||||
|
||||
const A_MOVE_LABELS: Record<string, string> = {
|
||||
reflection: "반영",
|
||||
validation: "타당화",
|
||||
open_question: "열린 질문",
|
||||
closed_question: "닫힌 질문",
|
||||
clarification: "명료화",
|
||||
confrontation: "직면",
|
||||
interpretation: "해석",
|
||||
advice: "조언",
|
||||
information: "정보 제공",
|
||||
self_disclosure: "자기 개방",
|
||||
topic_shift: "화제 전환",
|
||||
other: "기타",
|
||||
};
|
||||
const A_MOVE_ORDER = Object.keys(A_MOVE_LABELS);
|
||||
|
||||
function soreSpotLabel(choice: string): string {
|
||||
if (choice === "none") return "해당 없음";
|
||||
const match = /^spot_(\d+)$/.exec(choice);
|
||||
return match ? `민감 지점 ${match[1]}` : UNKNOWN_LABEL;
|
||||
}
|
||||
|
||||
function soreSpotRank(choice: string): number {
|
||||
if (choice === "none") return -1;
|
||||
const match = /^spot_(\d+)$/.exec(choice);
|
||||
return match ? Number(match[1]) : Number.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
export function appraisalChoiceLabel(key: string, choice: string): string {
|
||||
if (key === "a_coping") return A_COPING_LABELS[choice] ?? UNKNOWN_LABEL;
|
||||
if (key === "a_move") return A_MOVE_LABELS[choice] ?? UNKNOWN_LABEL;
|
||||
if (key === "a_sore_spot") return soreSpotLabel(choice);
|
||||
return UNKNOWN_LABEL;
|
||||
}
|
||||
|
||||
function sortByOrder(entries: Array<[string, number]>, order: string[]): Array<[string, number]> {
|
||||
return [...entries].sort((a, b) => {
|
||||
const rankA = order.indexOf(a[0]);
|
||||
const rankB = order.indexOf(b[0]);
|
||||
return (rankA === -1 ? order.length : rankA) - (rankB === -1 ? order.length : rankB);
|
||||
});
|
||||
}
|
||||
|
||||
export function orderedAppraisalChoiceEntries(key: string, probabilities: Record<string, number>): Array<[string, number]> {
|
||||
const entries = Object.entries(probabilities);
|
||||
if (key === "a_coping") return sortByOrder(entries, A_COPING_ORDER);
|
||||
if (key === "a_move") return sortByOrder(entries, A_MOVE_ORDER);
|
||||
if (key === "a_sore_spot") return [...entries].sort((a, b) => soreSpotRank(a[0]) - soreSpotRank(b[0]));
|
||||
return entries;
|
||||
}
|
||||
|
||||
function noulDecisionLabel(decision: string): string {
|
||||
if (decision === "true") return "예";
|
||||
if (decision === "false") return "아니오";
|
||||
if (decision === "uncertain") return "판단 보류";
|
||||
return UNKNOWN_LABEL;
|
||||
}
|
||||
|
||||
export function appraisalDecisionLabel(item: { key: string; kind: "noul" | "choice"; decision: string }): string {
|
||||
if (item.kind === "noul") return noulDecisionLabel(item.decision);
|
||||
if (item.decision === "uncertain") return "판단 보류";
|
||||
return appraisalChoiceLabel(item.key, item.decision);
|
||||
}
|
||||
|
||||
const C_BEHAVIOR_LABELS: Record<string, string> = {
|
||||
disclose_more: "더 털어놓음",
|
||||
stay_with_feeling: "감정에 머묾",
|
||||
hold_core: "핵심은 보류",
|
||||
ask_back: "되물음",
|
||||
minimal_response: "최소 반응",
|
||||
shift_topic: "화제 전환",
|
||||
abstract_talk: "추상적으로 말함",
|
||||
appease: "달래며 동의",
|
||||
self_blame: "자기비난",
|
||||
complain: "불만 표현",
|
||||
argue_back: "반박",
|
||||
take_control: "주도권 요구",
|
||||
};
|
||||
|
||||
function behaviorLabel(choice: string): string {
|
||||
return C_BEHAVIOR_LABELS[choice] ?? UNKNOWN_LABEL;
|
||||
}
|
||||
|
||||
export function behaviorOrUncertainLabel(value: string): string {
|
||||
return value === "uncertain" ? "판단 보류" : behaviorLabel(value);
|
||||
}
|
||||
|
||||
const C_DISPLAY_LABELS: Record<string, string> = {
|
||||
as_felt: "그대로",
|
||||
softened: "누그러뜨림",
|
||||
covered_by_agreement: "수긍으로 덮음",
|
||||
masked: "웃음·무덤덤함으로 가림",
|
||||
};
|
||||
|
||||
function displayLabel(choice: string): string {
|
||||
return C_DISPLAY_LABELS[choice] ?? UNKNOWN_LABEL;
|
||||
}
|
||||
|
||||
export function expressionDecisionLabel(kind: "behavior" | "display", decision: string): string {
|
||||
if (decision === "uncertain") return "판단 보류";
|
||||
return kind === "behavior" ? behaviorLabel(decision) : displayLabel(decision);
|
||||
}
|
||||
|
||||
export function expressionNoulDecisionLabel(decision: string): string {
|
||||
return noulDecisionLabel(decision);
|
||||
}
|
||||
|
||||
const GATE_REASON_LABELS: Record<string, string> = {
|
||||
openness_closed: "매우 닫힘으로 조정",
|
||||
openness_guarded: "경계 상태로 조정",
|
||||
};
|
||||
|
||||
export function gateReasonLabel(reason: string | null | undefined): string {
|
||||
if (reason === null || reason === undefined) return "조정 없음";
|
||||
return GATE_REASON_LABELS[reason] ?? UNKNOWN_LABEL;
|
||||
}
|
||||
|
||||
const STANCE_LABELS: Record<string, string> = {
|
||||
engage: "대화에 더 들어옴",
|
||||
cautious: "조심스러운 거리",
|
||||
pull_back: "한발 물러남",
|
||||
push_back: "맞섬·반박",
|
||||
};
|
||||
|
||||
export function stanceLabel(stance: string | null | undefined): string {
|
||||
if (stance === null || stance === undefined) return "판단 보류";
|
||||
return STANCE_LABELS[stance] ?? UNKNOWN_LABEL;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue