G0~G8 성과·동맹 측정 OS 작업 일괄 고정

8월 7일까지 워킹트리에만 남아 있던 미커밋 작업을 커밋한다. 여러 사본
폴더(worktree·clone)에 흩어져 있던 중간 스냅샷을 정리하기 전에 원본을
git 이력으로 고정하는 것이 목적이다.

- contracts/routes/services: measurement, outcome_trajectory, rupture_repair,
  deliberate_practice, calibration_transfer, supervision_research,
  multimodal_alliance, continuous_improvement 계열 신규 모듈과 테스트
- infra/db/init: 07~16 마이그레이션(측정 기반~calibration transfer 실행)
- apps/web: 세션 리뷰 카드·관리 화면·E2E 스펙 추가
- docs/ops: G0~G8 라이브 통합·배포·롤백 증거 문서와 evidence JSON/PNG
- scripts: smoke·ledger·릴리스 에이전트·NAS 프리뷰 운영 스크립트

engine.public 로그 .bak과 apps/web/test-results 산출물은 커밋에서 제외했다.
This commit is contained in:
Yun Chan 2026-08-08 01:30:53 +09:00
parent 93dd8f82d7
commit 16e791e044
390 changed files with 243188 additions and 499 deletions

View file

@ -0,0 +1,285 @@
"""종료된 재연습 회기의 durable evaluator 근거를 G4 시도로 변환한다.
adapter는 학습자의 자기평가나 브라우저가 보낸 성공/전이 플래그를 읽지
않는다. 저장된 UUID, 정규화된 fast-loop 라벨, 내담자 반응 라벨과 회기
identity만 사용하며 원문은 결과 원장에 복제하지 않는다.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Iterable
from uuid import UUID, uuid5
from ..contracts.deliberate_practice import (
CriterionObservation,
PracticeAttemptObservation,
PracticeEpisodeInput,
PracticeEvidenceRef,
PracticePrescription,
ScenarioNovelty,
)
_OBSERVER_NAMESPACE = UUID("2d2df938-056c-56cc-86d2-99ad53bd3507")
OBSERVER_VERSION = "practice-runtime-observer-v1"
_POSITIVE_CLIENT_STATES = frozenset(
{
"affect_contact",
"thought_organizing",
"responds_to_exploration",
"expresses_plan",
"defense_loosening",
}
)
_WITHDRAWN_CLIENT_STATES = frozenset(
{"defensive", "involuntary", "affect_masking", "active_passivity"}
)
class RuntimePracticeObservationError(ValueError):
"""durable 근거가 독립 판정에 충분하지 않을 때 fail-closed한다."""
@dataclass(frozen=True, slots=True)
class EvaluatedTurnPair:
counselor_turn_id: UUID
counselor_turn_seq: int
client_turn_id: UUID | None
client_turn_seq: int | None
technique_codes: tuple[str, ...]
client_state_codes: tuple[str, ...]
appropriateness: str
intent_deviation_dimensions: tuple[str, ...] = ()
evaluator_error: str | None = None
utterance_fingerprint: str | None = None
has_voice_feature: bool = False
def observation_model_run_id(
*, prescription_id: str, practice_session_id: UUID, counselor_turn_id: UUID
) -> UUID:
return uuid5(
_OBSERVER_NAMESPACE,
f"{OBSERVER_VERSION}:{prescription_id}:{practice_session_id}:{counselor_turn_id}",
)
def _target_techniques(competency_id: str) -> frozenset[str]:
key = competency_id.lower()
if any(token in key for token in ("empathy", "empathic", "reflection")):
return frozenset({"empathy", "reflection", "validation", "restatement"})
if any(token in key for token in ("open_question", "open-question")):
return frozenset({"facilitative_question", "exploration", "clarification"})
if any(token in key for token in ("rupture", "repair", "impact")):
return frozenset(
{"opinion_check", "validation", "reflection", "here_and_now_focus"}
)
if any(token in key for token in ("goal", "collaborative", "reagreement")):
return frozenset(
{"consent_motivation_check", "opinion_check", "restatement"}
)
if any(token in key for token in ("presence", "response-space")):
return frozenset({"holding", "reflection", "here_and_now_focus"})
raise RuntimePracticeObservationError(
f"unsupported runtime practice competency: {competency_id}"
)
def _novelty(
*,
source_case_id: UUID | None,
source_persona_id: UUID | None,
practice_case_id: UUID | None,
practice_persona_id: UUID | None,
) -> ScenarioNovelty:
# identity가 불완전하면 전이를 낙관적으로 추론하지 않는다.
if source_case_id is None or practice_case_id is None:
return "familiar"
if source_case_id == practice_case_id:
return "familiar"
if (
source_persona_id is not None
and practice_persona_id is not None
and source_persona_id == practice_persona_id
):
return "familiar"
return "unseen_transfer"
def _client_response(states: Iterable[str]) -> str:
normalized = frozenset(states)
positive = bool(normalized & _POSITIVE_CLIENT_STATES)
negative = bool(normalized & _WITHDRAWN_CLIENT_STATES)
if "compliant_surface" in normalized:
return "compliance_only"
if positive and negative:
return "mixed"
if "expresses_plan" in normalized and positive:
return "explicit_alignment"
if positive:
return "engaged"
if negative:
return "withdrawn"
return "mixed"
def derive_runtime_episode(
*,
prescription: PracticePrescription,
practice_session_id: UUID,
source_case_id: UUID | None,
source_persona_id: UUID | None,
practice_case_id: UUID | None,
practice_persona_id: UUID | None,
turn_pairs: Iterable[EvaluatedTurnPair],
) -> PracticeEpisodeInput:
targets = _target_techniques(prescription.competency_id)
pairs = tuple(sorted(turn_pairs, key=lambda item: item.counselor_turn_seq))
if not pairs:
raise RuntimePracticeObservationError(
"completed practice session has no durable counselor turn evidence"
)
novelty = _novelty(
source_case_id=source_case_id,
source_persona_id=source_persona_id,
practice_case_id=practice_case_id,
practice_persona_id=practice_persona_id,
)
scene_id = f"practice-session:{practice_session_id}"
variant_id = f"runtime-session-{practice_session_id}"
attempts: list[PracticeAttemptObservation] = []
for sequence_no, pair in enumerate(pairs, start=1):
model_run_id = observation_model_run_id(
prescription_id=prescription.prescription_id,
practice_session_id=practice_session_id,
counselor_turn_id=pair.counselor_turn_id,
)
learner_ref = PracticeEvidenceRef(
ref_id=str(pair.counselor_turn_id),
scene_id=scene_id,
turn_index=pair.counselor_turn_seq,
actor="learner",
kind="learner_behavior",
)
technique_match = bool(set(pair.technique_codes) & targets)
target_deviation = any(
token in prescription.competency_id.lower()
or token in prescription.criterion_id.lower()
for token in pair.intent_deviation_dimensions
if token
)
counterevidence: list[str] = []
if pair.client_turn_id is None or pair.client_turn_seq is None:
criterion = CriterionObservation(
criterion_id=prescription.criterion_id,
status="error",
source_kind="model_inferred",
perspective="independent_observer",
model_run_id=model_run_id,
evidence_refs=(),
counterevidence=(),
uncertainty=1.0,
error_code="client_response_turn_missing",
)
attempts.append(
PracticeAttemptObservation(
attempt_id=f"oas-g4-attempt-{practice_session_id.hex}-{sequence_no}",
prescription_id=prescription.prescription_id,
competency_id=prescription.competency_id,
sequence_no=sequence_no,
scenario_variant_id=variant_id,
scenario_novelty=novelty,
difficulty_level=prescription.activity.difficulty_level,
criterion=criterion,
evidence_refs=(learner_ref,),
uncertainty=1.0,
counterevidence=("client_response_turn_missing",),
utterance_template_id=pair.utterance_fingerprint,
error_code="client_response_turn_missing",
)
)
continue
response = _client_response(pair.client_state_codes)
observed = (
technique_match
and pair.appropriateness == "pos"
and not target_deviation
and not pair.evaluator_error
)
if not technique_match:
counterevidence.append("target_technique_not_observed")
if pair.appropriateness != "pos":
counterevidence.append("appropriateness_not_positive")
if target_deviation:
counterevidence.append("target_intent_deviation_observed")
if pair.evaluator_error:
counterevidence.append("turn_evaluation_error")
if response not in {"engaged", "explicit_alignment"}:
counterevidence.append("client_response_does_not_support_effect")
client_ref = PracticeEvidenceRef(
ref_id=str(pair.client_turn_id),
scene_id=scene_id,
turn_index=pair.client_turn_seq,
actor="client",
kind="client_response",
)
voice_refs: tuple[PracticeEvidenceRef, ...] = ()
if pair.has_voice_feature:
voice_refs = (
PracticeEvidenceRef(
ref_id=str(pair.counselor_turn_id),
scene_id=scene_id,
turn_index=pair.counselor_turn_seq,
actor="runtime",
kind="voice_feature",
),
)
criterion = CriterionObservation(
criterion_id=prescription.criterion_id,
status="observed" if observed else "not_observed",
source_kind="model_inferred",
perspective="independent_observer",
model_run_id=model_run_id,
evidence_refs=(learner_ref,) if observed else (),
counterevidence=tuple(counterevidence) if not observed else (),
uncertainty=0.25 if observed else 0.4,
)
attempts.append(
PracticeAttemptObservation(
attempt_id=f"oas-g4-attempt-{practice_session_id.hex}-{sequence_no}",
prescription_id=prescription.prescription_id,
competency_id=prescription.competency_id,
sequence_no=sequence_no,
scenario_variant_id=variant_id,
scenario_novelty=novelty,
difficulty_level=prescription.activity.difficulty_level,
criterion=criterion,
client_response=response,
evidence_refs=(
(client_ref, *voice_refs)
if observed
else (learner_ref, client_ref, *voice_refs)
),
uncertainty=0.25 if observed else 0.4,
counterevidence=tuple(counterevidence),
utterance_template_id=pair.utterance_fingerprint,
learner_claimed_success=False,
)
)
return PracticeEpisodeInput(
episode_id=f"oas-g4-episode-{practice_session_id.hex}",
prescription_id=prescription.prescription_id,
attempts=tuple(attempts),
)
__all__ = [
"EvaluatedTurnPair",
"OBSERVER_VERSION",
"RuntimePracticeObservationError",
"derive_runtime_episode",
"observation_model_run_id",
]