refactor(v026): #5 AiFailedReason union 단일 export 통합

기존 'unreachable' | 'schema' | 'timeout' | 'other' literal 이 3곳에 분산:
- telemetryEvents.ts (zod enum AiFailedReason)
- TelemetryService.ts (EmitInput 안 inline literal)
- AiWorker.ts (classifyReason 반환 + AiTelemetryEmitter inline literal)

zod enum z.infer 통해 type 파생, 단일 export AiFailedReason 으로 통합.
- AiFailedReasonSchema (zod enum) + AiFailedReason (type) 둘 다 export
- TelemetryService EmitInput / AiWorker classifyReason / AiTelemetryEmitter
  모두 import type AiFailedReason 사용

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
altair823
2026-05-05 01:29:11 +09:00
parent 3cfa60bbba
commit a2c17a8b0d
3 changed files with 8 additions and 5 deletions

View File

@@ -1,11 +1,12 @@
import type { NoteRepository } from '../repository/NoteRepository.js';
import type { Note } from '@shared/types';
import type { AiFailedReason } from '../services/telemetryEvents.js';
import { ProviderHolder } from './ProviderHolder.js';
import { parseAllCandidates } from '../services/dueDateParser.js';
import { ZodError } from 'zod';
import { kstTodayAsDate, kstTodayIso } from '../../shared/util/kstDate.js';
function classifyReason(err: unknown): 'unreachable' | 'schema' | 'timeout' | 'other' {
function classifyReason(err: unknown): AiFailedReason {
if (err instanceof ZodError) return 'schema';
const msg = err instanceof Error ? err.message.toLowerCase() : String(err).toLowerCase();
if (msg.includes('econnrefused') || msg.includes('enotfound') || msg.includes('fetch failed') || msg.includes('econnreset') || msg.includes('unreachable')) {
@@ -20,7 +21,7 @@ function classifyReason(err: unknown): 'unreachable' | 'schema' | 'timeout' | 'o
export interface AiTelemetryEmitter {
emit(input:
| { kind: 'ai_succeeded'; payload: { noteId: string; durationMs: number; attempts: number } }
| { kind: 'ai_failed'; payload: { noteId: string; reason: 'unreachable' | 'schema' | 'timeout' | 'other'; attempts: number } }
| { kind: 'ai_failed'; payload: { noteId: string; reason: AiFailedReason; attempts: number } }
| { kind: 'tag_vocab_hit'; payload: { tagId: number; vocabSize: number } }
| { kind: 'tag_vocab_miss'; payload: { vocabSize: number } }
): Promise<void>;

View File

@@ -1,6 +1,7 @@
import { mkdir, appendFile, readFile, readdir, unlink, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import { validateEvent, TelemetryEvent } from './telemetryEvents.js';
import type { AiFailedReason } from './telemetryEvents.js';
import { aggregateStats } from './telemetryStats.js';
import { kstTodayIso, DAY_MS } from '../../shared/util/kstDate.js';
@@ -11,7 +12,7 @@ export interface TelemetryServiceOptions {
export type EmitInput =
| { kind: 'capture'; payload: { noteId: string; rawTextLength: number; hasMedia: boolean } }
| { kind: 'ai_succeeded'; payload: { noteId: string; durationMs: number; attempts: number } }
| { kind: 'ai_failed'; payload: { noteId: string; reason: 'unreachable' | 'schema' | 'timeout' | 'other'; attempts: number } }
| { kind: 'ai_failed'; payload: { noteId: string; reason: AiFailedReason; attempts: number } }
| { kind: 'trash'; payload: { noteId: string } }
| { kind: 'restore'; payload: { noteId: string } }
| { kind: 'permanent_delete'; payload: { noteId: string } }

View File

@@ -12,11 +12,12 @@ const AiSucceededPayload = z.object({
attempts: z.number().int().nonnegative()
}).strict();
const AiFailedReason = z.enum(['unreachable', 'schema', 'timeout', 'other']);
export const AiFailedReasonSchema = z.enum(['unreachable', 'schema', 'timeout', 'other']);
export type AiFailedReason = z.infer<typeof AiFailedReasonSchema>;
const AiFailedPayload = z.object({
noteId: z.string().min(1),
reason: AiFailedReason,
reason: AiFailedReasonSchema,
attempts: z.number().int().nonnegative()
}).strict();