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,120 @@
import { api, supplementalApi } from "../../lib/api";
import type { components } from "../../lib/api.gen";
type ApiSchema<Name extends keyof components["schemas"]> =
components["schemas"][Name];
/** G5 wire DTO는 생성된 FastAPI OpenAPI 계약을 그대로 사용한다. */
export type CalibrationConfidenceInterval = ApiSchema<"ConfidenceInterval">;
export type CalibrationPair = ApiSchema<"CalibrationPair">;
export type CompetencyCalibrationAssessment =
ApiSchema<"CompetencyCalibrationAssessment">;
export type MetacognitivePrescription = ApiSchema<"MetacognitivePrescription">;
export type PredictionRevisionItem = ApiSchema<"PredictionRevisionItem">;
export type PredictionLockItem = ApiSchema<"PredictionLockItem">;
export type PerformanceObservationItem = ApiSchema<"PerformanceObservationItem">;
export type PredictionHistoryItem = ApiSchema<"PredictionHistoryItem">;
export type CalibrationAssessmentItem = ApiSchema<"CalibrationAssessmentItem">;
export type TransferAssessment = ApiSchema<"TransferAssessment">;
export type TransferTrialItem = ApiSchema<"TransferTrialItem">;
export type TransferAssessmentItem = ApiSchema<"TransferAssessmentItem">;
export type SyntheticSubgroupResult = ApiSchema<"SyntheticSubgroupResult">;
export type SubgroupDriftReport = ApiSchema<"SubgroupDriftReport">;
export type DriftReportItem = ApiSchema<"DriftReportItem">;
export type TransferSuiteItem = ApiSchema<"TransferSuiteItem">;
export type TeacherReviewItem = ApiSchema<"TeacherReviewItem">;
export type CalibrationTransferReadModelResponse =
ApiSchema<"CalibrationTransferReadModelResponse">;
export type PredictionRevisionRequest = ApiSchema<"PredictionRevisionRequest">;
export type PredictionRevisionResponse = ApiSchema<"PredictionRevisionResponse">;
export type PredictionLockRequest = ApiSchema<"PredictionLockRequest">;
export type PredictionLockResponse = ApiSchema<"PredictionLockResponse">;
export type TeacherCorrectionResponse = ApiSchema<"TeacherReviewResponse">;
export type ActualTransferAssessment = ApiSchema<"ActualTransferAssessment">;
export type ActualTransferExecutionRequest =
ApiSchema<"ActualTransferExecutionRequest">;
export type ActualTransferExecutionResponse =
ApiSchema<"ActualTransferExecutionResponse">;
export type CalibrationBias = CompetencyCalibrationAssessment["bias"];
export type CalibrationImprovement =
CompetencyCalibrationAssessment["improvement"];
export type PerformanceStatus = PerformanceObservationItem["status"];
export type RelationshipStyle = TransferTrialItem["relationship_style"];
export type DriftStatus = SubgroupDriftReport["status"];
/** 교수자 UI는 원판정 쓰기를 열지 않고 append-only 교정만 허용한다. */
export type TeacherCorrectionRequest = Omit<
ApiSchema<"TeacherReviewRequest">,
"disposition" | "correction_payload"
> & {
disposition: "corrected";
correction_payload: { teacher_note: string };
};
interface TeacherSessionRef {
session_id: string;
learner_id: string;
}
interface TeacherDashboardLookup {
pending_reviews?: TeacherSessionRef[];
recent_sessions?: TeacherSessionRef[];
}
function learnerPath(learnerId?: string): string {
return learnerId
? `/calibration/learners/${encodeURIComponent(learnerId)}`
: "/calibration/learners/me";
}
export class CalibrationLearnerContextError extends Error {
constructor() {
super("이 회기의 학습자 캘리브레이션 원장을 찾지 못했습니다.");
this.name = "CalibrationLearnerContextError";
}
}
export const calibrationTransferApi = {
getForLearner: (signal?: AbortSignal) =>
supplementalApi.get<CalibrationTransferReadModelResponse>(learnerPath(), {
signal,
}),
getForTeacherSession: async (sessionId: string, signal?: AbortSignal) => {
const dashboard = await supplementalApi.get<TeacherDashboardLookup>(
"/teacher/dashboard",
{ signal },
);
const session = [
...(dashboard.pending_reviews ?? []),
...(dashboard.recent_sessions ?? []),
].find((item) => item.session_id === sessionId);
if (!session) throw new CalibrationLearnerContextError();
return supplementalApi.get<CalibrationTransferReadModelResponse>(
learnerPath(session.learner_id),
{ signal },
);
},
appendPredictionRevision: (body: PredictionRevisionRequest) =>
api.post<PredictionRevisionResponse>(
"/calibration/predictions/revisions",
body,
),
lockPrediction: (historyId: string, body: PredictionLockRequest) =>
api.post<PredictionLockResponse>(
`/calibration/predictions/${encodeURIComponent(historyId)}/lock`,
body,
),
appendTeacherCorrection: (body: TeacherCorrectionRequest) =>
api.post<TeacherCorrectionResponse>("/calibration/reviews", body),
appendActualTransferExecution: (body: ActualTransferExecutionRequest) =>
api.post<ActualTransferExecutionResponse>(
"/calibration/transfer-executions",
body,
),
};