/** * 전략 DTO 정규화 — API·DB에서 문자열 JSON·flowLayout 보정 */ import type { LogicNode } from './strategyTypes'; import type { StrategyDto } from './backendApi'; import { extractVirtualConditions } from './virtualStrategyConditions'; import { decodeConditionForEditor, encodeConditionForSave } from './strategyConditionSerde'; import type { StrategyFlowLayoutStore } from './strategyEditorLayoutStorage'; export function asLogicNode(raw: unknown): LogicNode | null { if (!raw) return null; if (typeof raw === 'string') { const t = raw.trim(); if (!t) return null; try { return JSON.parse(t) as LogicNode; } catch { return null; } } if (typeof raw === 'object') return raw as LogicNode; return null; } export function normalizeMarketCode(market: string): string { const s = market.trim().toUpperCase(); if (!s) return 'KRW-BTC'; return s.startsWith('KRW-') ? s : `KRW-${s}`; } /** buy/sell DSL 파싱·flowLayout 재인코딩으로 조건 추출 가능 상태로 보정 */ export function hydrateStrategyDto(strategy: StrategyDto): StrategyDto { const buy = asLogicNode(strategy.buyCondition); const sell = asLogicNode(strategy.sellCondition); let next: StrategyDto = { ...strategy, buyCondition: buy ?? undefined, sellCondition: sell ?? undefined, }; if (extractVirtualConditions(next).length > 0) return next; const layout = strategy.flowLayout as StrategyFlowLayoutStore | null | undefined; if (!layout) return next; try { const buyState = decodeConditionForEditor(buy); const sellState = decodeConditionForEditor(sell); const encodedBuy = encodeConditionForSave(buyState); const encodedSell = encodeConditionForSave(sellState); if (encodedBuy || encodedSell) { next = { ...next, buyCondition: encodedBuy ?? buy ?? undefined, sellCondition: encodedSell ?? sell ?? undefined, }; } } catch { /* flowLayout 보정 실패 — 원본 유지 */ } return next; }