세션 평가·라이브코치·교수자 분석 라운드 마감 + 문서 정리 + 코드품질 리팩터
- 누적 작업트리 커밋: 회기 평가 복구·durable 저장, 라이브 코치 이력/근거, 교수자 학생분석, 음성 비언어 메타, PII 마스킹, 운영 티켓/헬스 등 - 문서: 완료 기록 docs/archive/ 냉동 보관, docs/ 단일 인덱스(docs/README.md)+통합 TODO(docs/TODO.md)로 정리 - 리팩터(행위 보존): Stage enum SSOT(taxonomy 소유·state_machine re-export), store recent/masked_turns 중복 제거, speaker_ko_label 단일 헬퍼, _list_sessions N+1 제거(state/turns 배치 + 턴평가 하이드레이션 배치) - 검증: 백엔드 pytest 352 passed, _list_sessions E2E chromium-single-run 2 passed
This commit is contained in:
parent
7c41c3ce79
commit
778e8526d4
108 changed files with 6457 additions and 455 deletions
|
|
@ -19,6 +19,9 @@ from .store import InProcSession, TurnRecord, store
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_LIVE_COACH_RECHARGE_MIN_RAPPORT = 0.35
|
||||
_LIVE_COACH_RECHARGE_MIN_OPENNESS_GAIN = 0.02
|
||||
|
||||
|
||||
class SessionAccessError(str, Enum):
|
||||
NOT_FOUND = "not_found"
|
||||
|
|
@ -179,6 +182,63 @@ async def record_safety_event(
|
|||
require_runtime_fallback_allowed("safety event")
|
||||
|
||||
|
||||
def should_recharge_live_coach_credit(
|
||||
evaluation: dict | None,
|
||||
before: state_machine.SessionState,
|
||||
after: state_machine.SessionState,
|
||||
) -> tuple[bool, str]:
|
||||
"""Good-score recharge gate based on stored evaluator/state-machine evidence."""
|
||||
if not isinstance(evaluation, dict):
|
||||
return False, ""
|
||||
if evaluation.get("appropriateness") != "pos":
|
||||
return False, ""
|
||||
try:
|
||||
rapport = float(evaluation.get("rapport_signal") or 0)
|
||||
except (TypeError, ValueError):
|
||||
rapport = 0.0
|
||||
if rapport < _LIVE_COACH_RECHARGE_MIN_RAPPORT:
|
||||
return False, ""
|
||||
|
||||
openness_gain = float(after.effective_openness or 0) - float(before.effective_openness or 0)
|
||||
stage_changed = after.stage != before.stage
|
||||
if not stage_changed and openness_gain < _LIVE_COACH_RECHARGE_MIN_OPENNESS_GAIN:
|
||||
return False, ""
|
||||
if stage_changed:
|
||||
return True, "좋은 발화로 내담자 단계가 열려 코칭 기회 1개를 충전했습니다."
|
||||
return True, "좋은 발화 뒤 내담자 개방도가 올라 코칭 기회 1개를 충전했습니다."
|
||||
|
||||
|
||||
async def maybe_recharge_live_coach_credit(
|
||||
sess: InProcSession,
|
||||
ctx: orchestrator.TurnContext,
|
||||
result: orchestrator.TurnResult,
|
||||
) -> None:
|
||||
"""Record one live-coach recharge when a strong learner turn changes client state."""
|
||||
assert ctx.state_after is not None
|
||||
should_recharge, reason = should_recharge_live_coach_credit(
|
||||
result.evaluation,
|
||||
ctx.state_before,
|
||||
result.state_after,
|
||||
)
|
||||
if not should_recharge:
|
||||
return
|
||||
try:
|
||||
await session_persistence.record_live_coach_recharge(
|
||||
session_id=sess.session_id,
|
||||
learner_id=sess.learner_id,
|
||||
turn_seq=result.turn_seq,
|
||||
stage=stage_label(result.state_after.stage),
|
||||
reason=reason,
|
||||
)
|
||||
except Exception:
|
||||
logger.exception(
|
||||
"live coach recharge persistence failed: session_id=%s turn_seq=%s",
|
||||
sess.session_id,
|
||||
result.turn_seq,
|
||||
)
|
||||
require_runtime_fallback_allowed("live coach recharge")
|
||||
|
||||
|
||||
async def finalize_completed_turn(
|
||||
sess: InProcSession,
|
||||
ctx: orchestrator.TurnContext,
|
||||
|
|
@ -195,6 +255,7 @@ async def finalize_completed_turn(
|
|||
context_prefix=context_prefix,
|
||||
counselor_turn=counselor_turn,
|
||||
)
|
||||
await maybe_recharge_live_coach_credit(sess, ctx, result)
|
||||
await record_safety_event(sess, ctx, result)
|
||||
|
||||
|
||||
|
|
@ -203,8 +264,10 @@ __all__ = [
|
|||
"append_completed_turn",
|
||||
"finalize_completed_turn",
|
||||
"load_owned_session",
|
||||
"maybe_recharge_live_coach_credit",
|
||||
"record_safety_event",
|
||||
"record_completed_turn",
|
||||
"should_recharge_live_coach_credit",
|
||||
"stage_label",
|
||||
"update_session_state",
|
||||
]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue