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
|
|
@ -23,14 +23,21 @@ API_ROOT = REPO_ROOT / "apps" / "api"
|
|||
FIXTURE_PATH = REPO_ROOT / "scripts" / "fixtures" / "jev-client-korean-cases.json"
|
||||
REQUIRED_STATE_KEYS = frozenset(
|
||||
{
|
||||
"persona",
|
||||
"memory",
|
||||
"recent_turns",
|
||||
"counselor_utterance",
|
||||
"previous_emotions",
|
||||
"current_state",
|
||||
"recent_turns",
|
||||
"client_profile",
|
||||
"pinned_facts",
|
||||
"recall_summary",
|
||||
"relationship",
|
||||
"previous_feelings",
|
||||
}
|
||||
)
|
||||
EMOTION_KEYS = frozenset(
|
||||
{"anxiety", "sadness", "anger", "shame", "guilt", "loneliness", "relief", "hope", "trust"}
|
||||
)
|
||||
MOOD_WORDS = frozenset({"absent", "slight", "moderate", "strong", "overwhelming"})
|
||||
OPENNESS_WORDS = frozenset({"closed", "guarded", "partly_open", "open", "deep"})
|
||||
RESISTANCE_WORDS = frozenset({"low", "moderate", "high"})
|
||||
SENSITIVE_FIELD_PATTERN = re.compile(r"(?:api[_-]?key|authorization|password|secret|token)", re.IGNORECASE)
|
||||
SECRET_VALUE_PATTERN = re.compile(r"(?:sk-|bearer\s+|AIza|AKIA)[A-Za-z0-9_\-]{8,}", re.IGNORECASE)
|
||||
|
||||
|
|
@ -93,33 +100,25 @@ def validate_case(case: Any, identifiers: set[str]) -> None:
|
|||
state = case["state"]
|
||||
if not isinstance(state, dict) or set(state) != REQUIRED_STATE_KEYS:
|
||||
raise ValueError("fixture_state_contract_invalid")
|
||||
persona = state["persona"]
|
||||
memory = state["memory"]
|
||||
client_profile = state["client_profile"]
|
||||
recent_turns = state["recent_turns"]
|
||||
emotions = state["previous_emotions"]
|
||||
current_state = state["current_state"]
|
||||
context = persona.get("context") if isinstance(persona, dict) else None
|
||||
feelings = state["previous_feelings"]
|
||||
relationship = state["relationship"]
|
||||
if (
|
||||
not isinstance(persona, dict)
|
||||
or set(persona) != {"affect_baseline", "context"}
|
||||
or not isinstance(persona["affect_baseline"], dict)
|
||||
or not all(isinstance(value, (int, float)) and math.isfinite(value) for value in persona["affect_baseline"].values())
|
||||
or not isinstance(context, dict)
|
||||
or set(context) != {
|
||||
"big5", "resistance", "speech_style", "presenting", "history", "ccd", "triggers"
|
||||
}
|
||||
or not all(isinstance(context[key], dict) for key in ("big5", "resistance", "speech_style", "ccd"))
|
||||
or not all(isinstance(context[key], str) and context[key] for key in ("presenting", "history"))
|
||||
or not isinstance(context["triggers"], list)
|
||||
or not all(isinstance(trigger, str) and trigger for trigger in context["triggers"])
|
||||
not isinstance(client_profile, dict)
|
||||
or not {"presenting", "history", "sore_spots", "forbidden"} <= set(client_profile)
|
||||
or not all(isinstance(client_profile[key], str) and client_profile[key] for key in ("presenting", "history"))
|
||||
or not isinstance(client_profile["sore_spots"], list)
|
||||
or not isinstance(client_profile["forbidden"], list)
|
||||
or not all(isinstance(item, str) and item for item in client_profile["sore_spots"])
|
||||
or not all(isinstance(item, str) and item for item in client_profile["forbidden"])
|
||||
or ("temperament" in client_profile and not isinstance(client_profile["temperament"], list))
|
||||
):
|
||||
raise ValueError("fixture_persona_invalid")
|
||||
raise ValueError("fixture_client_profile_invalid")
|
||||
if (
|
||||
not isinstance(memory, dict)
|
||||
or set(memory) != {"recall_summary", "pinned_facts"}
|
||||
or not isinstance(memory["recall_summary"], str)
|
||||
or not isinstance(memory["pinned_facts"], list)
|
||||
or not all(isinstance(item, str) for item in memory["pinned_facts"])
|
||||
not isinstance(state["pinned_facts"], list)
|
||||
or not all(isinstance(item, str) for item in state["pinned_facts"])
|
||||
or not isinstance(state["recall_summary"], str)
|
||||
):
|
||||
raise ValueError("fixture_memory_invalid")
|
||||
if (
|
||||
|
|
@ -136,19 +135,20 @@ def validate_case(case: Any, identifiers: set[str]) -> None:
|
|||
):
|
||||
raise ValueError("fixture_recent_turns_invalid")
|
||||
if (
|
||||
not isinstance(emotions, dict)
|
||||
or set(emotions) != {
|
||||
"anxiety", "sadness", "anger", "shame", "guilt", "loneliness", "relief", "hope", "trust"
|
||||
}
|
||||
or not all(isinstance(value, (int, float)) and 0.0 <= value <= 1.0 for value in emotions.values())
|
||||
not isinstance(feelings, dict)
|
||||
or set(feelings) != EMOTION_KEYS
|
||||
or not all(value in MOOD_WORDS for value in feelings.values())
|
||||
):
|
||||
raise ValueError("fixture_previous_emotions_invalid")
|
||||
raise ValueError("fixture_previous_feelings_invalid")
|
||||
if (
|
||||
not isinstance(current_state, dict)
|
||||
or set(current_state) != {"resistance", "effective_openness"}
|
||||
or not all(isinstance(value, (int, float)) and 0.0 <= value <= 1.0 for value in current_state.values())
|
||||
not isinstance(relationship, dict)
|
||||
or set(relationship) != {"stage", "openness", "resistance"}
|
||||
or not isinstance(relationship["stage"], str)
|
||||
or not relationship["stage"]
|
||||
or relationship["openness"] not in OPENNESS_WORDS
|
||||
or relationship["resistance"] not in RESISTANCE_WORDS
|
||||
):
|
||||
raise ValueError("fixture_current_state_invalid")
|
||||
raise ValueError("fixture_relationship_invalid")
|
||||
questions = case["review_questions"]
|
||||
if (
|
||||
not isinstance(questions, list)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue