441 lines
20 KiB
JavaScript
441 lines
20 KiB
JavaScript
(function (root, factory) {
|
|
"use strict";
|
|
|
|
const api = Object.freeze(factory());
|
|
if (typeof module === "object" && module.exports) module.exports = api;
|
|
if (root) root.MbnOperatorWorkflow = api;
|
|
})(typeof globalThis === "object" ? globalThis : this, function () {
|
|
"use strict";
|
|
|
|
const DEFAULT_FADE_DURATION = 6;
|
|
const MAX_TEXT_LENGTH = 128;
|
|
const identifierPattern = /^[A-Za-z0-9_-]{1,128}$/;
|
|
const stockCodePattern = /^[A-Za-z0-9]{1,32}$/;
|
|
const controlCharacterPattern = /[\u0000-\u001f\u007f]/;
|
|
const runtimeStockAsset = Object.freeze({
|
|
relativePath: "bin/Debug/Res/종목.ini",
|
|
sha256: "45DFB1804828F0E4A45F73D681AF0709CE5662872FAA49CFC9F7A0B940409E20"
|
|
});
|
|
|
|
function freezeCut(cut) {
|
|
return Object.freeze({
|
|
...cut,
|
|
section: cut.group,
|
|
alias: cut.aliases[0],
|
|
cutCode: cut.aliases[0],
|
|
aliases: Object.freeze([...cut.aliases])
|
|
});
|
|
}
|
|
|
|
// bin/Debug/Res/종목.ini (SHA-256 45DFB180...940409E20), in runtime order.
|
|
// The two '-' separator rows are deliberately absent. The source RES copy differs
|
|
// at the after-hours label, so the operator-visible runtime asset is authoritative.
|
|
const stockCuts = Object.freeze([
|
|
freezeCut({ id: "basic-expected", label: "1열판기본_예상체결가", group: "1열판 기본", detail: "예상체결가", builderKey: "s5001", aliases: ["5001"], kind: "basic", subtype: "EXPECTED_OPENING" }),
|
|
freezeCut({ id: "basic-current", label: "1열판기본_현재가", group: "1열판 기본", detail: "현재가", builderKey: "s5001", aliases: ["5001", "N5001"], kind: "basic", subtype: "CURRENT" }),
|
|
freezeCut({ id: "basic-after-hours", label: "1열판기본_시간외단일가", group: "1열판 기본", detail: "시간외단일가", builderKey: "s5001", aliases: ["5001"], kind: "basic", subtype: "AFTER_HOURS_SINGLE_PRICE" }),
|
|
|
|
freezeCut({ id: "detail-open", label: "1열판상세_시가", group: "1열판 상세", detail: "시가", builderKey: "s5006", aliases: ["5006"], kind: "open-detail", subtype: "시가" }),
|
|
freezeCut({ id: "detail-face-value", label: "1열판상세_액면가", group: "1열판 상세", detail: "액면가", builderKey: "s5011", aliases: ["5011"], kind: "detail", subtype: "FaceValue" }),
|
|
freezeCut({ id: "detail-pbr", label: "1열판상세_PBR", group: "1열판 상세", detail: "PBR", builderKey: "s5011", aliases: ["5011"], kind: "detail", subtype: "Valuation" }),
|
|
freezeCut({ id: "detail-volume", label: "1열판상세_거래량", group: "1열판 상세", detail: "거래량", builderKey: "s5011", aliases: ["5011"], kind: "detail", subtype: "Volume" }),
|
|
|
|
freezeCut({ id: "yield-5d", label: "수익률그래프_5일", group: "수익률 그래프", detail: "5일", builderKey: "s5086", aliases: ["5086"], kind: "yield", subtype: "5일" }),
|
|
freezeCut({ id: "yield-20d", label: "수익률그래프_20일", group: "수익률 그래프", detail: "20일", builderKey: "s5086", aliases: ["5086"], kind: "yield", subtype: "20일" }),
|
|
freezeCut({ id: "yield-60d", label: "수익률그래프_60일", group: "수익률 그래프", detail: "60일", builderKey: "s5086", aliases: ["5086"], kind: "yield", subtype: "60일" }),
|
|
freezeCut({ id: "yield-120d", label: "수익률그래프_120일", group: "수익률 그래프", detail: "120일", builderKey: "s5086", aliases: ["5086"], kind: "yield", subtype: "120일" }),
|
|
freezeCut({ id: "yield-240d", label: "수익률그래프_240일", group: "수익률 그래프", detail: "240일", builderKey: "s5086", aliases: ["5086"], kind: "yield", subtype: "240일" }),
|
|
|
|
freezeCut({ id: "candle-daily", label: "캔들그래프_일봉", group: "캔들 그래프", detail: "일봉", builderKey: "s8010", aliases: ["8035"], kind: "candle", subtype: "PRICE" }),
|
|
freezeCut({ id: "candle-5d", label: "캔들그래프_5일", group: "캔들 그래프", detail: "5일", builderKey: "s8010", aliases: ["8061"], kind: "candle", subtype: "PRICE" }),
|
|
freezeCut({ id: "candle-20d", label: "캔들그래프_20일", group: "캔들 그래프", detail: "20일", builderKey: "s8010", aliases: ["8040"], kind: "candle", subtype: "PRICE" }),
|
|
freezeCut({ id: "candle-60d", label: "캔들그래프_60일", group: "캔들 그래프", detail: "60일", builderKey: "s8010", aliases: ["8046"], kind: "candle", subtype: "PRICE" }),
|
|
freezeCut({ id: "candle-120d", label: "캔들그래프_120일", group: "캔들 그래프", detail: "120일", builderKey: "s8010", aliases: ["8051"], kind: "candle", subtype: "PRICE" }),
|
|
freezeCut({ id: "candle-240d", label: "캔들그래프_240일", group: "캔들 그래프", detail: "240일", builderKey: "s8010", aliases: ["8056"], kind: "candle", subtype: "PRICE" }),
|
|
|
|
freezeCut({ id: "candle-volume-daily", label: "캔들그래프(거래량)_일봉", group: "캔들 그래프 · 거래량", detail: "일봉", builderKey: "s8010", aliases: ["8035"], kind: "candle", subtype: "VOLUME" }),
|
|
freezeCut({ id: "candle-volume-5d", label: "캔들그래프(거래량)_5일", group: "캔들 그래프 · 거래량", detail: "5일", builderKey: "s8010", aliases: ["8061"], kind: "candle", subtype: "VOLUME" }),
|
|
freezeCut({ id: "candle-volume-20d", label: "캔들그래프(거래량)_20일", group: "캔들 그래프 · 거래량", detail: "20일", builderKey: "s8010", aliases: ["8040"], kind: "candle", subtype: "VOLUME" }),
|
|
freezeCut({ id: "candle-volume-60d", label: "캔들그래프(거래량)_60일", group: "캔들 그래프 · 거래량", detail: "60일", builderKey: "s8010", aliases: ["8046"], kind: "candle", subtype: "VOLUME" }),
|
|
freezeCut({ id: "candle-volume-120d", label: "캔들그래프(거래량)_120일", group: "캔들 그래프 · 거래량", detail: "120일", builderKey: "s8010", aliases: ["8051"], kind: "candle", subtype: "VOLUME" }),
|
|
freezeCut({ id: "candle-volume-240d", label: "캔들그래프(거래량)_240일", group: "캔들 그래프 · 거래량", detail: "240일", builderKey: "s8010", aliases: ["8056"], kind: "candle", subtype: "VOLUME" }),
|
|
|
|
freezeCut({ id: "candle-expected-5d", label: "캔들그래프(예상체결가)_5일", group: "캔들 그래프 · 예상체결가", detail: "5일", builderKey: "s8010", aliases: ["8061"], kind: "candle", subtype: "EXPECTED" }),
|
|
freezeCut({ id: "candle-expected-20d", label: "캔들그래프(예상체결가)_20일", group: "캔들 그래프 · 예상체결가", detail: "20일", builderKey: "s8010", aliases: ["8040"], kind: "candle", subtype: "EXPECTED" }),
|
|
freezeCut({ id: "candle-expected-60d", label: "캔들그래프(예상체결가)_60일", group: "캔들 그래프 · 예상체결가", detail: "60일", builderKey: "s8010", aliases: ["8046"], kind: "candle", subtype: "EXPECTED" }),
|
|
freezeCut({ id: "candle-expected-120d", label: "캔들그래프(예상체결가)_120일", group: "캔들 그래프 · 예상체결가", detail: "120일", builderKey: "s8010", aliases: ["8051"], kind: "candle", subtype: "EXPECTED" }),
|
|
freezeCut({ id: "candle-expected-240d", label: "캔들그래프(예상체결가)_240일", group: "캔들 그래프 · 예상체결가", detail: "240일", builderKey: "s8010", aliases: ["8056"], kind: "candle", subtype: "EXPECTED" }),
|
|
|
|
freezeCut({ id: "order-book", label: "호가창_표그래프", group: "표 그래프", detail: "호가창", builderKey: "s8003", aliases: ["8003"], kind: "order-book", subtype: "TABLE_GRAPH" }),
|
|
freezeCut({ id: "traders", label: "거래원_표그래프", group: "표 그래프", detail: "거래원", builderKey: "s5037", aliases: ["5037"], kind: "traders", subtype: "TABLE_GRAPH" })
|
|
]);
|
|
|
|
if (stockCuts.length !== 31 || new Set(stockCuts.map(cut => cut.id)).size !== 31) {
|
|
throw new Error("The legacy stock workflow must contain exactly 31 unique cut variants.");
|
|
}
|
|
|
|
function manualAction(id, label, builderKey, alias, table, prerequisite) {
|
|
return Object.freeze({
|
|
id,
|
|
label,
|
|
builderKey,
|
|
alias,
|
|
cutCode: alias,
|
|
aliases: Object.freeze([alias]),
|
|
available: false,
|
|
autoAdd: false,
|
|
status: "unavailable",
|
|
prerequisiteTable: table,
|
|
prerequisites: Object.freeze([
|
|
`선택 종목에 대한 ${table} 수동 입력 데이터`,
|
|
prerequisite,
|
|
"수동 데이터 검증과 명시적 플레이리스트 추가 흐름"
|
|
])
|
|
});
|
|
}
|
|
|
|
// These buttons opened GraphE/INPUT_* manual-data workflows in the source program.
|
|
// A stock search result alone is not sufficient input, so they must never auto-add a cut.
|
|
const manualStockActions = Object.freeze([
|
|
manualAction("manual-revenue-mix", "주요매출 구성", "s5076", "5076", "INPUT_PIE", "구성 항목과 비중 및 기준일 입력"),
|
|
manualAction("manual-growth-metrics", "성장성 지표", "s5079", "5079", "INPUT_GROW", "성장성 지표 값과 분기 입력"),
|
|
manualAction("manual-sales", "매출액", "s5080", "5080", "INPUT_SELL", "기간별 매출액 수동 값 입력"),
|
|
manualAction("manual-operating-profit", "영업이익", "s5081", "5081", "INPUT_PROFIT", "기간별 영업이익 수동 값 입력")
|
|
]);
|
|
|
|
const cutsById = new Map(stockCuts.map(cut => [cut.id, cut]));
|
|
|
|
function valueFrom(raw, names) {
|
|
for (const name of names) {
|
|
if (Object.prototype.hasOwnProperty.call(raw, name) && raw[name] !== undefined && raw[name] !== null) {
|
|
return raw[name];
|
|
}
|
|
}
|
|
|
|
const keys = Object.keys(raw);
|
|
for (const name of names) {
|
|
const key = keys.find(candidate => candidate.toLocaleUpperCase("en-US") === name.toLocaleUpperCase("en-US"));
|
|
if (key !== undefined && raw[key] !== undefined && raw[key] !== null) return raw[key];
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function cleanText(value) {
|
|
if (typeof value !== "string") return null;
|
|
const text = value.trim();
|
|
return text.length > 0 && text.length <= MAX_TEXT_LENGTH && !controlCharacterPattern.test(text)
|
|
? text
|
|
: null;
|
|
}
|
|
|
|
function normalizeMarket(value) {
|
|
const text = String(value ?? "")
|
|
.trim()
|
|
.toLocaleUpperCase("en-US")
|
|
.replace(/[\s-]+/g, "_");
|
|
const isNxt = text.includes("NXT");
|
|
if (["KOSPI", "KOSPI_STOCK", "코스피", "코스피종목", "NXT_KOSPI", "KOSPI_NXT", "코스피_NXT", "NXT코스피"].includes(text)) {
|
|
return { market: "kospi", isNxt };
|
|
}
|
|
if (["KOSDAQ", "KOSDAQ_STOCK", "코스닥", "코스닥종목", "NXT_KOSDAQ", "KOSDAQ_NXT", "코스닥_NXT", "NXT코스닥"].includes(text)) {
|
|
return { market: "kosdaq", isNxt };
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function normalizeBoolean(value) {
|
|
if (value === true || value === false) return value;
|
|
if (value === 1 || value === "1") return true;
|
|
if (value === 0 || value === "0") return false;
|
|
return null;
|
|
}
|
|
|
|
function validateStockResult(raw) {
|
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
return { valid: false, error: "stock-object-required", value: null };
|
|
}
|
|
|
|
const marketValue = valueFrom(raw, ["market", "MARKET", "marketName", "MARKET_NAME", "groupCode", "GROUP_CODE"]);
|
|
const normalizedMarket = normalizeMarket(marketValue);
|
|
if (!normalizedMarket) return { valid: false, error: "unsupported-market", value: null };
|
|
|
|
const rawStockName = cleanText(valueFrom(raw, [
|
|
"stockName", "STOCK_NAME", "F_STOCK_NAME", "F_STOCK_WANNAME", "F_KNAM", "F_INPUT_NAME", "NAME", "WANNAME"
|
|
]));
|
|
const rawDisplayName = cleanText(valueFrom(raw, [
|
|
"displayName", "DISPLAY_NAME", "F_STOCK_WANNAME", "F_STOCK_NAME", "F_KNAM", "F_INPUT_NAME", "NAME", "WANNAME"
|
|
]));
|
|
if (!rawStockName && !rawDisplayName) {
|
|
return { valid: false, error: "stock-name-required", value: null };
|
|
}
|
|
|
|
const rawCode = valueFrom(raw, [
|
|
"stockCode", "STOCK_CODE", "F_STOCK_CODE", "F_SYMB", "SYMB", "F_PART_CODE", "CODE"
|
|
]);
|
|
const stockCode = typeof rawCode === "number" && Number.isSafeInteger(rawCode) && rawCode >= 0
|
|
? String(rawCode)
|
|
: cleanText(rawCode);
|
|
if (!stockCode || !stockCodePattern.test(stockCode)) {
|
|
return { valid: false, error: "invalid-stock-code", value: null };
|
|
}
|
|
|
|
const explicitNxtValue = valueFrom(raw, ["isNxt", "IS_NXT", "nxt", "NXT"]);
|
|
const explicitNxt = explicitNxtValue === undefined ? null : normalizeBoolean(explicitNxtValue);
|
|
if (explicitNxtValue !== undefined && explicitNxt === null) {
|
|
return { valid: false, error: "invalid-nxt-flag", value: null };
|
|
}
|
|
|
|
const nameImpliesNxt = `${rawStockName || ""} ${rawDisplayName || ""}`.includes("(NXT)");
|
|
const inferredNxt = normalizedMarket.isNxt || nameImpliesNxt;
|
|
if (explicitNxt === false && inferredNxt) {
|
|
return { valid: false, error: "inconsistent-nxt-selection", value: null };
|
|
}
|
|
const isNxt = explicitNxt === true || inferredNxt;
|
|
|
|
const withoutNxtSuffix = value => value.replace(/\s*\(NXT\)\s*/g, "").trim();
|
|
const stockName = cleanText(withoutNxtSuffix(rawStockName || rawDisplayName));
|
|
if (!stockName) return { valid: false, error: "stock-name-required", value: null };
|
|
const baseDisplayName = cleanText(rawDisplayName || rawStockName);
|
|
if (!baseDisplayName) return { valid: false, error: "display-name-required", value: null };
|
|
const displayName = isNxt
|
|
? `${withoutNxtSuffix(baseDisplayName)}(NXT)`
|
|
: baseDisplayName;
|
|
|
|
return {
|
|
valid: true,
|
|
error: null,
|
|
value: Object.freeze({
|
|
market: normalizedMarket.market,
|
|
stockName,
|
|
displayName,
|
|
stockCode,
|
|
isNxt
|
|
})
|
|
};
|
|
}
|
|
|
|
function normalizeStockResult(raw) {
|
|
const validation = validateStockResult(raw);
|
|
return validation.valid ? validation.value : null;
|
|
}
|
|
|
|
function getStockCut(cutId) {
|
|
return typeof cutId === "string" ? cutsById.get(cutId) || null : null;
|
|
}
|
|
|
|
function isStockCutAllowed(stock, cutId) {
|
|
const normalized = normalizeStockResult(stock);
|
|
const cut = getStockCut(cutId);
|
|
return Boolean(normalized && cut && (!normalized.isNxt || cut.id === "basic-current"));
|
|
}
|
|
|
|
function requireOptions(options) {
|
|
if (!options || typeof options !== "object" || Array.isArray(options)) {
|
|
throw new TypeError("Stock playlist options are required.");
|
|
}
|
|
const id = cleanText(options.id);
|
|
if (!id || !identifierPattern.test(id)) {
|
|
throw new TypeError("A safe stock playlist entry id is required.");
|
|
}
|
|
|
|
const ma5 = options.ma5 === undefined ? false : normalizeBoolean(options.ma5);
|
|
const ma20 = options.ma20 === undefined ? false : normalizeBoolean(options.ma20);
|
|
if (ma5 === null || ma20 === null) {
|
|
throw new TypeError("Moving-average flags must be boolean values.");
|
|
}
|
|
|
|
const fadeDuration = options.fadeDuration === undefined
|
|
? DEFAULT_FADE_DURATION
|
|
: Number(options.fadeDuration);
|
|
if (!Number.isInteger(fadeDuration) || fadeDuration < 0 || fadeDuration > 60) {
|
|
throw new RangeError("Fade duration must be an integer from 0 through 60.");
|
|
}
|
|
return { id, ma5, ma20, fadeDuration };
|
|
}
|
|
|
|
function marketGroup(stock, stockSuffix) {
|
|
const prefix = stock.isNxt ? "NXT_" : "";
|
|
return `${prefix}${stock.market.toLocaleUpperCase("en-US")}${stockSuffix}`;
|
|
}
|
|
|
|
function selectionFor(stock, cut, movingAverages) {
|
|
const common = {
|
|
subject: stock.displayName,
|
|
dataCode: stock.stockCode
|
|
};
|
|
switch (cut.kind) {
|
|
case "basic":
|
|
return {
|
|
groupCode: marketGroup(stock, ""),
|
|
...common,
|
|
graphicType: "1열판기본",
|
|
subtype: cut.subtype
|
|
};
|
|
case "open-detail":
|
|
return {
|
|
groupCode: marketGroup(stock, "_STOCK"),
|
|
...common,
|
|
graphicType: "1열판상세",
|
|
subtype: cut.subtype
|
|
};
|
|
case "detail":
|
|
return {
|
|
groupCode: marketGroup(stock, "_STOCK"),
|
|
...common,
|
|
graphicType: "1열판상세",
|
|
subtype: cut.subtype
|
|
};
|
|
case "yield":
|
|
return {
|
|
groupCode: marketGroup(stock, ""),
|
|
...common,
|
|
graphicType: "수익률그래프",
|
|
subtype: cut.subtype
|
|
};
|
|
case "candle": {
|
|
const flags = [];
|
|
if (movingAverages.ma5) flags.push("MA5");
|
|
if (movingAverages.ma20) flags.push("MA20");
|
|
return {
|
|
groupCode: marketGroup(stock, "_STOCK"),
|
|
...common,
|
|
graphicType: flags.join(","),
|
|
subtype: cut.subtype
|
|
};
|
|
}
|
|
case "order-book":
|
|
return {
|
|
groupCode: marketGroup(stock, ""),
|
|
...common,
|
|
graphicType: "ORDER_BOOK",
|
|
subtype: "TABLE_GRAPH"
|
|
};
|
|
case "traders":
|
|
return {
|
|
groupCode: marketGroup(stock, ""),
|
|
...common,
|
|
graphicType: "TRADER",
|
|
subtype: "TABLE_GRAPH"
|
|
};
|
|
default:
|
|
throw new Error("The stock cut mapping is unavailable.");
|
|
}
|
|
}
|
|
|
|
function createStockPlaylistEntry(stock, cutId, options) {
|
|
const normalized = normalizeStockResult(stock);
|
|
if (!normalized) throw new TypeError("A valid selected stock is required.");
|
|
const cut = getStockCut(cutId);
|
|
if (!cut) throw new RangeError("The selected stock cut is unknown.");
|
|
if (!isStockCutAllowed(normalized, cut.id)) {
|
|
throw new RangeError("NXT stocks support only 1열판기본_현재가.");
|
|
}
|
|
|
|
const normalizedOptions = requireOptions(options);
|
|
const movingAverages = Object.freeze({
|
|
ma5: cut.kind === "candle" && normalizedOptions.ma5,
|
|
ma20: cut.kind === "candle" && normalizedOptions.ma20
|
|
});
|
|
const selection = Object.freeze(selectionFor(normalized, cut, movingAverages));
|
|
const code = normalized.isNxt ? "N5001" : cut.aliases[0];
|
|
const operator = Object.freeze({
|
|
source: "legacy-stock-workflow",
|
|
cutId: cut.id,
|
|
legacyLabel: cut.label,
|
|
legacyGroup: cut.group,
|
|
legacyDetail: cut.detail,
|
|
stock: normalized,
|
|
movingAverages
|
|
});
|
|
|
|
return {
|
|
id: normalizedOptions.id,
|
|
builderKey: cut.builderKey,
|
|
code,
|
|
aliases: [...cut.aliases],
|
|
title: normalized.displayName,
|
|
detail: cut.label,
|
|
category: "stock",
|
|
market: normalized.market,
|
|
stockName: normalized.stockName,
|
|
displayName: normalized.displayName,
|
|
stockCode: normalized.stockCode,
|
|
isNxt: normalized.isNxt,
|
|
selection,
|
|
enabled: true,
|
|
fadeDuration: normalizedOptions.fadeDuration,
|
|
graphicType: selection.graphicType,
|
|
subtype: selection.subtype,
|
|
operator
|
|
};
|
|
}
|
|
|
|
function sameStringArray(left, right) {
|
|
return Array.isArray(left) && Array.isArray(right) &&
|
|
left.length === right.length && left.every((value, index) => value === right[index]);
|
|
}
|
|
|
|
function sameSelection(left, right) {
|
|
if (!left || typeof left !== "object" || !right || typeof right !== "object") return false;
|
|
return ["groupCode", "subject", "graphicType", "subtype", "dataCode"]
|
|
.every(key => typeof left[key] === "string" && left[key] === right[key]);
|
|
}
|
|
|
|
function restoreStockPlaylistEntry(value) {
|
|
if (!value || typeof value !== "object" || Array.isArray(value) ||
|
|
typeof value.enabled !== "boolean") {
|
|
return null;
|
|
}
|
|
const operator = value.operator;
|
|
if (!operator || typeof operator !== "object" ||
|
|
operator.source !== "legacy-stock-workflow" ||
|
|
typeof operator.cutId !== "string" ||
|
|
!operator.movingAverages || typeof operator.movingAverages !== "object" ||
|
|
typeof operator.movingAverages.ma5 !== "boolean" ||
|
|
typeof operator.movingAverages.ma20 !== "boolean") {
|
|
return null;
|
|
}
|
|
|
|
let recreated;
|
|
try {
|
|
recreated = createStockPlaylistEntry(operator.stock, operator.cutId, {
|
|
id: value.id,
|
|
ma5: operator.movingAverages.ma5,
|
|
ma20: operator.movingAverages.ma20,
|
|
fadeDuration: value.fadeDuration
|
|
});
|
|
} catch {
|
|
return null;
|
|
}
|
|
|
|
const trustedScalarKeys = [
|
|
"id", "builderKey", "code", "title", "detail", "category", "market",
|
|
"stockName", "displayName", "stockCode", "isNxt", "fadeDuration",
|
|
"graphicType", "subtype"
|
|
];
|
|
if (!trustedScalarKeys.every(key => value[key] === recreated[key]) ||
|
|
!sameStringArray(value.aliases, recreated.aliases) ||
|
|
!sameSelection(value.selection, recreated.selection) ||
|
|
operator.legacyLabel !== recreated.operator.legacyLabel ||
|
|
operator.legacyGroup !== recreated.operator.legacyGroup ||
|
|
operator.legacyDetail !== recreated.operator.legacyDetail) {
|
|
return null;
|
|
}
|
|
|
|
return { ...recreated, enabled: value.enabled };
|
|
}
|
|
|
|
return {
|
|
runtimeStockAsset,
|
|
stockCuts,
|
|
manualStockActions,
|
|
normalizeMarket,
|
|
validateStockResult,
|
|
normalizeStockResult,
|
|
getStockCut,
|
|
isStockCutAllowed,
|
|
createStockPlaylistEntry,
|
|
restoreStockPlaylistEntry
|
|
};
|
|
});
|