export interface NumericalDataStatus { modelName: string; jobName: string; lastStatus: 'COMPLETED' | 'FAILED' | 'STARTED' | 'UNKNOWN'; lastDataDate: string | null; // 데이터 기준일 (YYYY-MM-DD) lastDownloadedAt: string | null; // 마지막 실행 완료 시각 (ISO) nextScheduledAt: string | null; // Quartz 다음 예정 시각 (ISO) durationSec: number | null; // 소요 시간 (초) consecutiveFailures: number; // 연속 실패 횟수 } // ============================================================ // Mock 데이터 (Spring Batch/Quartz DB 연동 전) // DB 연동 준비 완료 후 getMockNumericalDataStatus → getActualNumericalDataStatus 교체 // ============================================================ const MOCK_DATA: NumericalDataStatus[] = [ { modelName: 'HYCOM', jobName: 'downloadHycomJob', lastStatus: 'COMPLETED', lastDataDate: '2026-03-25', lastDownloadedAt: '2026-03-25T06:12:34', nextScheduledAt: '2026-03-25T12:00:00', durationSec: 342, consecutiveFailures: 0, }, { modelName: 'GFS', jobName: 'downloadGfsJob', lastStatus: 'COMPLETED', lastDataDate: '2026-03-25', lastDownloadedAt: '2026-03-25T06:48:11', nextScheduledAt: '2026-03-25T12:00:00', durationSec: 518, consecutiveFailures: 0, }, { modelName: 'WW3', jobName: 'downloadWw3Job', lastStatus: 'FAILED', lastDataDate: '2026-03-24', lastDownloadedAt: '2026-03-25T07:03:55', nextScheduledAt: '2026-03-25T13:00:00', durationSec: null, consecutiveFailures: 2, }, { modelName: 'KOAST POS_WIND', jobName: 'downloadKoastWindJob', lastStatus: 'COMPLETED', lastDataDate: '2026-03-25', lastDownloadedAt: '2026-03-25T07:21:05', nextScheduledAt: '2026-03-25T13:00:00', durationSec: 127, consecutiveFailures: 0, }, { modelName: 'KOAST POS_HYDR', jobName: 'downloadKoastHydrJob', lastStatus: 'COMPLETED', lastDataDate: '2026-03-25', lastDownloadedAt: '2026-03-25T07:35:48', nextScheduledAt: '2026-03-25T13:00:00', durationSec: 183, consecutiveFailures: 0, }, { modelName: 'KOAST POS_WAVE', jobName: 'downloadKoastWaveJob', lastStatus: 'COMPLETED', lastDataDate: '2026-03-25', lastDownloadedAt: '2026-03-25T07:52:19', nextScheduledAt: '2026-03-25T13:00:00', durationSec: 156, consecutiveFailures: 0, }, ]; export async function getNumericalDataStatus(): Promise { // TODO: Spring Batch + Quartz DB 테이블 생성 후 아래 실제 쿼리로 교체 // // import { wingDb } from '../db/wingDb.js' // // -- 각 Job의 최신 실행 결과 조회 (BATCH_JOB_EXECUTION) // SELECT // ji.JOB_NAME, // je.START_TIME, je.END_TIME, // je.STATUS, je.EXIT_CODE, je.EXIT_MESSAGE, // jep.STRING_VAL AS data_date, // EXTRACT(EPOCH FROM (je.END_TIME - je.START_TIME))::INT AS duration_sec // FROM BATCH_JOB_EXECUTION je // JOIN BATCH_JOB_INSTANCE ji ON je.JOB_INSTANCE_ID = ji.JOB_INSTANCE_ID // LEFT JOIN BATCH_JOB_EXECUTION_PARAMS jep // ON je.JOB_EXECUTION_ID = jep.JOB_EXECUTION_ID // AND jep.KEY_NAME = 'data_date' // WHERE je.JOB_EXECUTION_ID IN ( // SELECT MAX(je2.JOB_EXECUTION_ID) // FROM BATCH_JOB_EXECUTION je2 // GROUP BY je2.JOB_INSTANCE_ID // ) // ORDER BY je.START_TIME DESC; // // -- Quartz 다음 실행 예정 시각 (NEXT_FIRE_TIME은 epoch milliseconds) // SELECT JOB_NAME, to_timestamp(NEXT_FIRE_TIME / 1000) AS next_fire_time // FROM QRTZ_TRIGGERS; // // -- 연속 실패 횟수 집계 (최근 실행부터 COMPLETED 전까지 카운트) // SELECT ji.JOB_NAME, COUNT(*) AS consecutive_failures // FROM BATCH_JOB_EXECUTION je // JOIN BATCH_JOB_INSTANCE ji ON je.JOB_INSTANCE_ID = ji.JOB_INSTANCE_ID // WHERE je.STATUS = 'FAILED' // AND je.JOB_EXECUTION_ID > ( // SELECT COALESCE(MAX(je2.JOB_EXECUTION_ID), 0) // FROM BATCH_JOB_EXECUTION je2 // WHERE je2.JOB_INSTANCE_ID = je.JOB_INSTANCE_ID // AND je2.STATUS = 'COMPLETED' // ) // GROUP BY ji.JOB_NAME; return MOCK_DATA; }