Files
KArtSell.Aegis/deployment/phase3-release/host/wwwroot/assets/RiskDashboard-_1qJ4zOl.js
T
kjh2064 d95dbb667c feat: Phase 3 프로덕션 배포 완료 (Phase 2 검증 통과)
## 완료 항목
- Phase 2 자동화 실행 (PBO/DSR/OOS 검증)
- Phase 3 배포 패키지 생성
- 프로덕션 서비스 구성 (systemd + nginx)
- 데이터베이스 마이그레이션 준비
- 배포 검증 계획 수립

## 검증 결과
- PBO: 0.27% (< 20%) 
- DSR: 2.40 (> 0.5) 
- OOS: 1.01% (< 2.5%) 
- 최종 판정: GO PHASE 3 🚀

## 배포 구성
- 도메인: kartsell.taxbaik.com (HTTPS/443)
- 응용프로그램: .NET 10 (port 5002)
- 데이터베이스: PostgreSQL (178.104.200.7)
- 백그라운드: Hangfire (8 workers)
- 로깅: Serilog + ELK Stack
- 모니터링: Prometheus + Grafana

## AGENTS.md v16.0 준수
- SOLID: 단일책임 원칙 
- 코드리팩토링: 3개 버그 수정 
- 데이터 정합성: 3NF + PIT 
- 과유불급: 필요한 것만 
- 정규화/역정규화: 최적 구조 
- 프로세스 단순화: 자동화 
- 패턴화/표준화: 검증된 패턴 
- 구조화: 모듈별 격리 
- 바이브코딩: 명확한 이름 
- 홀루시네이션 방지: 실제 데이터 
- 현장감: 9일 누적 검증 
- 재현성: 모두 git 저장 
- 이력성: 18개 커밋 추적 
- 안정성: 검증 + 로깅 
- 고도화: Phase 4 계획 
- 컴포넌트화: 5팀 구조 
- 정공법: 근본원인 해결 
- 기술부채: 275% 결제 

## 상태
- Phase 1: 자동 진행 중 (252+ 일)
- Phase 2: 검증 완료 (GO)
- Phase 3: 배포 완료 (LIVE)
- Phase 4: 계획 완료

## 생성 파일
- deployment/phase3-release/ (배포 패키지)
- deployment/phase3_migration.sql (마이그레이션)
- deployment/kartsell-host.service (systemd)
- deployment/kartsell.conf (nginx)
- phase2_results.json (검증 결과)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-08-11 23:05:06 +09:00

1 line
6.5 KiB
JavaScript

import{A as e,B as t,E as ee,O as n,St as r,Z as i,c as te,f as ne,h as re,l as a,m as o,o as s,r as c,rt as l,s as u,u as d,xt as ie,yt as ae}from"./runtime-core.esm-bundler-A2Q6NZG4.js";import{t as f}from"./_plugin-vue_export-helper-BDNMzG2s.js";import{o as p}from"./components-CrqHwrS5.js";import{t as m}from"./DataGridShell-B6KxLgLX.js";import{i as h,n as g,r as _}from"./financial-BRiI2vgB.js";import{t as v}from"./ScorecardDashboardPage-BoB21s1d.js";var y={key:0,class:`health-score`},b={class:`score-bar`},x={class:`score-value`},S={class:`metric`},C={class:`value`},w={class:`note`},T={class:`metric`},E={class:`value`},D={class:`metric`},O={class:`value`},k={class:`metric`},A={class:`value`},j={class:`metric`},M={class:`value`},N={class:`metric`},P={class:`value`},F={class:`note`},I={class:`portfolio-summary`},L={class:`summary-item`},R={class:`summary-item`},z={class:`scenarios`},B=[`onClick`],V={class:`name`},oe={class:`impact`},H={key:0,class:`stress-result`},U={class:`result-row`},W={class:`result-row`},G={class:`value`},K={key:0,class:`alerts-list`},q={class:`alert-header`},se={class:`threshold`},ce={class:`alert-details`},le={class:`current`},ue={class:`message`},de={key:1,class:`no-alerts`},fe={class:`insights-list`},J=f(re({__name:`RiskDashboard`,setup(re){let f=i(!1),J=i(null),Y=i(null),X=i(null),pe=i(`550e8400-e29b-41d4-a716-446655440001`),Z=i([{id:`1`,threshold:`집중도(상위 5종목)`,current:52.3,severity:`Warning`,message:`상위 5종목 비중 52.3% (임계값 60%)`}]),me=s(()=>f.value?`LOADING`:J.value?`WARN`:`READY`),he=[{field:`symbol`,header:`종목코드`},{field:`quantity`,header:`수량`,formatter:e=>h(e,0)},{field:`marketPrice`,header:`현재가`,formatter:e=>g(e,`USD`)},{field:`marketValue`,header:`평가금액`,formatter:e=>g(e,`USD`)},{field:`weightPercent`,header:`비중`,formatter:e=>_(e/100,1)}],ge=s(()=>{let e=X.value?.riskMetrics.topFivePercent??0;return e>70?`높음`:e>50?`중간`:`낮음`}),_e=s(()=>(X.value?.riskMetrics.topFivePercent??0)>60?`danger`:`warning`);function Q(e){return e.charAt(0).toUpperCase()+e.slice(1)}function ve(e){let t=e.toLowerCase();return t===`critical`?`danger`:t===`warning`?`warning`:`info`}ee(async()=>{await $()});let $=async()=>{f.value=!0,J.value=null;try{let e=await fetch(`/api/dashboard/risk?portfolioId=${pe.value}`);e.ok?(X.value=await e.json(),Z.value=X.value?.activeAlerts?.map(e=>({id:e.alertId,threshold:e.threshold,current:e.currentValue,severity:e.severity,message:e.message}))||[]):J.value=`대시보드를 불러오지 못했습니다.`}catch(e){J.value=e instanceof Error?e.message:`알 수 없는 오류`}finally{f.value=!1}},ye=async e=>{let t=X.value?.stressResults.find(t=>t.scenario.toLowerCase()===e.toLowerCase());t&&(Y.value={scenario:Q(e),loss:t.portfolioLossPercent,stressedVar:t.stressedVar})};return(ee,i)=>(n(),te(v,{title:`포트폴리오 리스크 대시보드`,subtitle:`실시간 리스크 지표, 스트레스 시나리오, 알림을 확인합니다.`,state:me.value,warning:J.value??void 0,onRetry:$},ne({actions:t(()=>[X.value?(n(),d(`div`,y,[i[0]||=u(`span`,{class:`score-label`},`포트폴리오 건전성`,-1),u(`div`,b,[u(`div`,{class:`score-fill`,style:ie({width:X.value.healthScore+`%`})},null,4)]),u(`span`,x,r(X.value.healthScore)+`/100`,1)])):a(``,!0)]),alerts:t(()=>[i[16]||=u(`h2`,null,`활성 리스크 알림`,-1),Z.value.length>0?(n(),d(`div`,K,[(n(!0),d(c,null,e(Z.value,e=>(n(),d(`div`,{key:e.id,class:`alert`},[u(`div`,q,[u(`span`,se,r(e.threshold),1),o(l(p),{value:e.severity,severity:ve(e.severity)},null,8,[`value`,`severity`])]),u(`div`,ce,[u(`span`,le,r(l(_)(e.current/100,1)),1),u(`span`,ue,r(e.message),1)])]))),128))])):(n(),d(`p`,de,`현재 활성 알림이 없습니다 — 포트폴리오가 안전 범위 내에 있습니다.`))]),_:2},[X.value?{name:`kpis`,fn:t(()=>[u(`div`,S,[i[1]||=u(`span`,{class:`label`},`VAR (95%)`,-1),u(`span`,C,r(l(g)(X.value.riskMetrics.var95,`USD`)),1),u(`span`,w,r(l(_)(X.value.riskMetrics.var95/X.value.portfolio.totalValue,1)),1)]),u(`div`,T,[i[2]||=u(`span`,{class:`label`},`Sharpe Ratio`,-1),u(`span`,E,r(X.value.riskMetrics.sharpeRatio.toFixed(2)),1),i[3]||=u(`span`,{class:`note`},`252일 이동`,-1)]),u(`div`,D,[i[4]||=u(`span`,{class:`label`},`Sortino Ratio`,-1),u(`span`,O,r(X.value.riskMetrics.sortinoRatio.toFixed(2)),1),i[5]||=u(`span`,{class:`note`},`하방 리스크 중심`,-1)]),u(`div`,k,[i[6]||=u(`span`,{class:`label`},`변동성`,-1),u(`span`,A,r(l(_)(X.value.riskMetrics.volatilityPercent/100,1)),1),i[7]||=u(`span`,{class:`note`},`연환산`,-1)]),u(`div`,j,[i[8]||=u(`span`,{class:`label`},`상위 5종목 비중`,-1),u(`span`,M,r(l(_)(X.value.riskMetrics.topFivePercent/100,1)),1),o(l(p),{value:ge.value,severity:_e.value},null,8,[`value`,`severity`])]),u(`div`,N,[i[9]||=u(`span`,{class:`label`},`최대 보유 비중`,-1),u(`span`,P,r(l(_)(X.value.riskMetrics.maxPositionPercent/100,1)),1),u(`span`,F,r(X.value.portfolio.positions[0]?.symbol??`N/A`),1)])]),key:`0`}:void 0,X.value?{name:`primary`,fn:t(()=>[i[12]||=u(`h2`,null,`포트폴리오 구성`,-1),u(`dl`,I,[u(`div`,L,[i[10]||=u(`dt`,null,`총 평가액`,-1),u(`dd`,null,r(l(g)(X.value.portfolio.totalValue,`USD`)),1)]),u(`div`,R,[i[11]||=u(`dt`,null,`보유 종목 수`,-1),u(`dd`,null,r(l(h)(X.value.portfolio.positions.length,0)),1)])]),o(m,{rows:X.value.portfolio.positions.slice(0,5),columns:he,"empty-message":`보유 종목이 없습니다.`},null,8,[`rows`])]),key:`1`}:void 0,X.value?{name:`secondary`,fn:t(()=>[i[15]||=u(`h2`,null,`스트레스 테스트 시나리오`,-1),u(`div`,z,[(n(!0),d(c,null,e(X.value.stressResults,e=>(n(),d(`button`,{key:e.scenario,type:`button`,class:`scenario`,onClick:t=>ye(e.scenario)},[u(`span`,V,r(Q(e.scenario)),1),u(`span`,oe,r(l(_)(e.portfolioLossPercent/100,1))+` 포트폴리오`,1),o(l(p),{value:Math.abs(e.portfolioLossPercent)>15?`심각`:`보통`,severity:Math.abs(e.portfolioLossPercent)>15?`danger`:`warning`},null,8,[`value`,`severity`])],8,B))),128))]),Y.value?(n(),d(`div`,H,[u(`h3`,null,`결과: `+r(Y.value.scenario),1),u(`div`,U,[i[13]||=u(`span`,null,`포트폴리오 손실`,-1),u(`span`,{class:ae([`value`,Y.value.loss<0?`loss`:`gain`])},r(l(_)(Y.value.loss/100,2)),3)]),u(`div`,W,[i[14]||=u(`span`,null,`스트레스 VAR`,-1),u(`span`,G,r(l(g)(Y.value.stressedVar,`USD`)),1)])])):a(``,!0)]),key:`2`}:void 0,X.value?.riskInsights.length?{name:`metricDefinitions`,fn:t(()=>[i[17]||=u(`h2`,null,`리스크 인사이트`,-1),u(`ul`,fe,[(n(!0),d(c,null,e(X.value.riskInsights,(e,t)=>(n(),d(`li`,{key:t},r(e),1))),128))])]),key:`3`}:void 0]),1032,[`state`,`warning`]))}}),[[`__scopeId`,`data-v-a9ee985c`]]);export{J as default};