Initial commit: Add project files
ci / backend (push) Failing after 12s
ci / frontend (push) Failing after 19s
ci / static (push) Failing after 45s

This commit is contained in:
2026-08-02 05:15:36 +09:00
commit dcd1322d41
636 changed files with 122352 additions and 0 deletions
@@ -0,0 +1,7 @@
import { api } from '../../shared/api/client'
import { modelOperationsPlanSchema, type ModelOperationsPlan } from './schema'
export async function getModelOperationsPlan(): Promise<ModelOperationsPlan> {
const response = await api.get('/internal/v1/model-operations/plan')
return modelOperationsPlanSchema.parse(response.data)
}
@@ -0,0 +1,31 @@
<script setup lang="ts">
defineProps<{
algorithmStatus: string
orderCapability: string
modelMutationBoundary: string
}>()
</script>
<template>
<section aria-labelledby="automation-boundary-title">
<h2 id="automation-boundary-title">자동화 경계</h2>
<dl>
<div>
<dt>알고리즘 상태</dt>
<dd>{{ algorithmStatus }}</dd>
</div>
<div>
<dt>주문 Capability</dt>
<dd>{{ orderCapability }}</dd>
</div>
<div>
<dt>모델 변경</dt>
<dd>{{ modelMutationBoundary }}</dd>
</div>
</dl>
<p>
스케줄러는 평가 증거와 개선 제안만 생성합니다. 모델 승격·롤백·임계값 변경은 독립 검증과
maker-checker 승인을 거쳐야 합니다.
</p>
</section>
</template>
@@ -0,0 +1,39 @@
<script setup lang="ts">
import type { ModelOperationItem } from '../schema'
defineProps<{ operations: ModelOperationItem[] }>()
</script>
<template>
<section aria-labelledby="operation-plan-title">
<h2 id="operation-plan-title">지속 평가·개선 작업 계획</h2>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>Job</th>
<th>작업</th>
<th>주기</th>
<th>자동화 모드</th>
<th>Queue</th>
<th>Gate</th>
<th>Owner</th>
<th>산출물</th>
</tr>
</thead>
<tbody>
<tr v-for="operation in operations" :key="operation.operationCode">
<td>{{ operation.operationCode }}</td>
<td>{{ operation.name }}</td>
<td>{{ operation.cadence }}</td>
<td>{{ operation.automationMode }}</td>
<td>{{ operation.queue }}</td>
<td>{{ operation.gate }}</td>
<td>{{ operation.primaryOwner }} / {{ operation.secondaryOwner }}</td>
<td>{{ operation.output }}</td>
</tr>
</tbody>
</table>
</div>
</section>
</template>
@@ -0,0 +1,32 @@
<script setup lang="ts">
import QueryStateBoundary from '../../../shared/ui/QueryStateBoundary.vue'
import AutomationBoundaryPanel from '../components/AutomationBoundaryPanel.vue'
import ModelOperationTable from '../components/ModelOperationTable.vue'
import { useModelOperationsPlanQuery } from '../queries'
const planQuery = useModelOperationsPlanQuery()
</script>
<template>
<article>
<header>
<h1>모델 운영·지속 고도화</h1>
<p>···분기 평가, drift, champion/challenger, 개선 제안과 승격 증거를 관리합니다.</p>
</header>
<QueryStateBoundary
:loading="planQuery.isLoading.value"
:error="planQuery.error.value as Error | null"
:empty="!planQuery.data.value"
>
<template v-if="planQuery.data.value">
<AutomationBoundaryPanel
:algorithm-status="planQuery.data.value.algorithmStatus"
:order-capability="planQuery.data.value.orderCapability"
:model-mutation-boundary="planQuery.data.value.modelMutationBoundary"
/>
<ModelOperationTable :operations="planQuery.data.value.operations" />
</template>
</QueryStateBoundary>
</article>
</template>
@@ -0,0 +1,16 @@
import { useQuery } from '@tanstack/vue-query'
import { getModelOperationsPlan } from './api'
export const modelOperationsKeys = {
all: ['model-operations'] as const,
plan: () => [...modelOperationsKeys.all, 'plan'] as const
}
export function useModelOperationsPlanQuery() {
return useQuery({
queryKey: modelOperationsKeys.plan(),
queryFn: getModelOperationsPlan,
staleTime: 5 * 60 * 1000,
retry: 1
})
}
@@ -0,0 +1,24 @@
import { z } from 'zod'
export const operationItemSchema = z.object({
operationCode: z.string().min(1),
name: z.string().min(1),
cadence: z.enum(['DAILY', 'WEEKLY', 'MONTHLY', 'QUARTERLY', 'EVENT_DRIVEN']),
automationMode: z.enum(['EVALUATION_ONLY', 'PROPOSAL_ONLY', 'DRILL_ONLY']),
queue: z.string().min(1),
primaryOwner: z.string().min(1),
secondaryOwner: z.string().min(1),
requiredEvidence: z.string().min(1),
output: z.string().min(1),
gate: z.string().min(1)
})
export const modelOperationsPlanSchema = z.object({
algorithmStatus: z.literal('RESEARCH_CANDIDATE_NOT_PRODUCTION'),
orderCapability: z.literal('AUTOMATIC_ORDER_AND_KIS_SUBMISSION_OFF'),
modelMutationBoundary: z.literal('EVALUATION_AND_PROPOSAL_ONLY_HUMAN_APPROVAL_REQUIRED'),
operations: z.array(operationItemSchema)
})
export type ModelOperationsPlan = z.infer<typeof modelOperationsPlanSchema>
export type ModelOperationItem = z.infer<typeof operationItemSchema>
@@ -0,0 +1,26 @@
import { describe, expect, it } from 'vitest'
import { modelOperationsPlanSchema } from '../schema'
describe('model operations plan schema', () => {
it('rejects an automatic promotion mode', () => {
const result = modelOperationsPlanSchema.safeParse({
algorithmStatus: 'RESEARCH_CANDIDATE_NOT_PRODUCTION',
orderCapability: 'AUTOMATIC_ORDER_AND_KIS_SUBMISSION_OFF',
modelMutationBoundary: 'EVALUATION_AND_PROPOSAL_ONLY_HUMAN_APPROVAL_REQUIRED',
operations: [{
operationCode: 'J22',
name: 'PromotionEvidenceReviewBuild',
cadence: 'MONTHLY',
automationMode: 'AUTO_PROMOTE',
queue: 'q-control',
primaryOwner: 'Risk',
secondaryOwner: 'Compliance',
requiredEvidence: 'all evidence',
output: 'review packet',
gate: 'G4'
}]
})
expect(result.success).toBe(false)
})
})