Initial commit: Add project files
This commit is contained in:
@@ -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)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user