Jev 내담자 평가·표현 v2와 속마음 공개

상담자 발화 판정(A)·감정(B)·표현(C) 20문항 질문 세트, 감쇠 없는 이번 턴 반응과 비대칭 기분 전이, 개방도 게이트로 생성 지시를 만들고 ccd.coping_strategy 전달 누락을 고친다.

trace v2와 고정 문구 속마음 요약(migration 24, AI 경로 차단 RLS)을 같은 트랜잭션에 저장하고 피드백 정책이 켜진 경우에만 done·TurnResponse·음성 reply·리뷰로 노출한다. 회기 화면 속마음 보기 토글, 리뷰 접힘 블록, 관리자 감정 관측 v2 표시를 추가한다.
This commit is contained in:
Yun Chan 2026-09-30 13:23:09 +09:00
parent 36cb847e38
commit 29c406d89f
39 changed files with 5726 additions and 343 deletions

View file

@ -36,11 +36,13 @@ from ..session_evaluation_timeout import (
session_evaluation_stale_after_seconds,
session_evaluation_transport_timeout_seconds,
)
from ..contracts.client_affect import ClientInnerReactionV1
from ..services import (
client_affect,
evaluator,
feedback_policy,
guardrail,
inner_reaction_exposure,
live_coach,
memory,
notifications,
@ -191,6 +193,8 @@ class TurnResponse(BaseModel):
output_error: Optional[str] = None
# P2 단계 누적 게이지·상세 수치 — 턴마다 갱신된 파생값.
progress: Optional[SessionProgress] = None
# 학습자·교수자용 속마음 요약(§8.2·§9). 저장 성공 && 피드백 정책 켜짐일 때만 값.
inner_reaction: Optional[ClientInnerReactionV1] = None
class SessionEndResponse(BaseModel):
@ -1779,8 +1783,13 @@ async def get_session_review(
session_id,
review_principal,
)
inner_reactions = await session_persistence.list_client_inner_reactions(
session_id,
review_principal,
)
else:
evaluation_record, evaluation_durable = None, True
inner_reactions = {}
saved_worksheet_payload, _ = await session_persistence.load_case_worksheet(
session_id,
review_principal,
@ -1807,6 +1816,7 @@ async def get_session_review(
teacher_review_record=teacher_review_record,
learner_feedback_enabled=learner_feedback_enabled,
expose_learner_feedback=expose_learner_feedback,
inner_reactions=inner_reactions,
)
)
@ -1968,6 +1978,13 @@ async def submit_turn(
prev_rapport_credit=sess.prev_rapport_credit,
goal_stages=list(sess.goal_stages or []),
),
inner_reaction=inner_reaction_exposure.expose_client_inner_reaction(
ctx.client_inner_reaction,
stored=True,
feedback_enabled=feedback_policy.effective_learner_feedback_enabled(
sess, principal
),
),
)
@ -2157,6 +2174,22 @@ async def stream_turn(
context_prefix="session",
recharge_live_coach=False,
)
exposed_inner_reaction = (
inner_reaction_exposure.expose_client_inner_reaction(
ctx.client_inner_reaction,
stored=True,
feedback_enabled=(
feedback_policy.effective_learner_feedback_enabled(
sess, principal
)
),
)
)
data["inner_reaction"] = (
exposed_inner_reaction.model_dump(mode="json")
if exposed_inner_reaction is not None
else None
)
_schedule_stream_turn_evaluation(
sess=sess,
ctx=ctx,

View file

@ -42,7 +42,9 @@ from ..runtime_policy import require_runtime_fallback_allowed
from ..session_turn_memory import build_turn_memory
from ..services import (
evaluator,
feedback_policy,
guardrail,
inner_reaction_exposure,
multimodal_alliance,
multimodal_alliance_store,
orchestrator,
@ -1382,6 +1384,14 @@ async def _run_turn_and_speak(
),
)
exposed_inner_reaction = inner_reaction_exposure.expose_client_inner_reaction(
ctx.client_inner_reaction,
stored=True,
feedback_enabled=feedback_policy.effective_learner_feedback_enabled(
sess, context.principal
),
)
# Send the final client text before audio playback.
await _safe_send_json(
websocket,
@ -1401,6 +1411,11 @@ async def _run_turn_and_speak(
prev_rapport_credit=sess.prev_rapport_credit,
goal_stages=list(sess.goal_stages or []),
).model_dump(),
"inner_reaction": (
exposed_inner_reaction.model_dump(mode="json")
if exposed_inner_reaction is not None
else None
),
},
)