feat(screener): canvas adds AI news node (12 nodes, 18 edges)
This commit is contained in:
@@ -8,6 +8,7 @@ export const NODE_IDS = {
|
|||||||
RS: 'score-rs-rating',
|
RS: 'score-rs-rating',
|
||||||
MA: 'score-ma-alignment',
|
MA: 'score-ma-alignment',
|
||||||
VCP: 'score-vcp-lite',
|
VCP: 'score-vcp-lite',
|
||||||
|
AI_NEWS: 'score-ai-news',
|
||||||
COMBINE: 'combine',
|
COMBINE: 'combine',
|
||||||
RESULT: 'result',
|
RESULT: 'result',
|
||||||
};
|
};
|
||||||
@@ -22,6 +23,7 @@ export const NODE_KIND_MAP = {
|
|||||||
[NODE_IDS.RS]: 'score',
|
[NODE_IDS.RS]: 'score',
|
||||||
[NODE_IDS.MA]: 'score',
|
[NODE_IDS.MA]: 'score',
|
||||||
[NODE_IDS.VCP]: 'score',
|
[NODE_IDS.VCP]: 'score',
|
||||||
|
[NODE_IDS.AI_NEWS]: 'score',
|
||||||
[NODE_IDS.COMBINE]: 'combine',
|
[NODE_IDS.COMBINE]: 'combine',
|
||||||
[NODE_IDS.RESULT]: 'result',
|
[NODE_IDS.RESULT]: 'result',
|
||||||
};
|
};
|
||||||
@@ -35,6 +37,7 @@ export const SCORE_NODE_NAME_MAP = {
|
|||||||
[NODE_IDS.RS]: 'rs_rating',
|
[NODE_IDS.RS]: 'rs_rating',
|
||||||
[NODE_IDS.MA]: 'ma_alignment',
|
[NODE_IDS.MA]: 'ma_alignment',
|
||||||
[NODE_IDS.VCP]: 'vcp_lite',
|
[NODE_IDS.VCP]: 'vcp_lite',
|
||||||
|
[NODE_IDS.AI_NEWS]: 'ai_news',
|
||||||
};
|
};
|
||||||
|
|
||||||
// 4단 layout: DATA → GATE → (점수 7개 세로) → COMBINE → RESULT
|
// 4단 layout: DATA → GATE → (점수 7개 세로) → COMBINE → RESULT
|
||||||
@@ -48,11 +51,12 @@ export const INITIAL_NODE_POSITIONS = {
|
|||||||
[NODE_IDS.RS]: { x: 480, y: 360 },
|
[NODE_IDS.RS]: { x: 480, y: 360 },
|
||||||
[NODE_IDS.MA]: { x: 480, y: 450 },
|
[NODE_IDS.MA]: { x: 480, y: 450 },
|
||||||
[NODE_IDS.VCP]: { x: 480, y: 540 },
|
[NODE_IDS.VCP]: { x: 480, y: 540 },
|
||||||
|
[NODE_IDS.AI_NEWS]: { x: 480, y: 630 },
|
||||||
[NODE_IDS.COMBINE]: { x: 800, y: 280 },
|
[NODE_IDS.COMBINE]: { x: 800, y: 280 },
|
||||||
[NODE_IDS.RESULT]: { x: 1080, y: 280 },
|
[NODE_IDS.RESULT]: { x: 1080, y: 280 },
|
||||||
};
|
};
|
||||||
|
|
||||||
const SCORE_KEYS = ['FOREIGN','VOLUME','MOMENTUM','HIGH52W','RS','MA','VCP'];
|
const SCORE_KEYS = ['FOREIGN','VOLUME','MOMENTUM','HIGH52W','RS','MA','VCP','AI_NEWS'];
|
||||||
|
|
||||||
export const EDGES = [
|
export const EDGES = [
|
||||||
{ id: 'e-data-gate', source: NODE_IDS.DATA, target: NODE_IDS.GATE },
|
{ id: 'e-data-gate', source: NODE_IDS.DATA, target: NODE_IDS.GATE },
|
||||||
@@ -77,4 +81,5 @@ export const SCORE_NODE_LABEL = {
|
|||||||
[NODE_IDS.RS]: { icon: '💪', title: 'RS Rating' },
|
[NODE_IDS.RS]: { icon: '💪', title: 'RS Rating' },
|
||||||
[NODE_IDS.MA]: { icon: '📈', title: '이평선 정렬' },
|
[NODE_IDS.MA]: { icon: '📈', title: '이평선 정렬' },
|
||||||
[NODE_IDS.VCP]: { icon: '🌀', title: 'VCP-lite' },
|
[NODE_IDS.VCP]: { icon: '🌀', title: 'VCP-lite' },
|
||||||
|
[NODE_IDS.AI_NEWS]: { icon: '🤖', title: 'AI 뉴스' },
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import {
|
|||||||
} from './canvasLayout';
|
} from './canvasLayout';
|
||||||
|
|
||||||
describe('canvasLayout', () => {
|
describe('canvasLayout', () => {
|
||||||
it('NODE_IDS — 11개 키, 모두 unique', () => {
|
it('NODE_IDS — 12개 키, 모두 unique', () => {
|
||||||
const ids = Object.values(NODE_IDS);
|
const ids = Object.values(NODE_IDS);
|
||||||
expect(ids).toHaveLength(11);
|
expect(ids).toHaveLength(12);
|
||||||
expect(new Set(ids).size).toBe(11);
|
expect(new Set(ids).size).toBe(12);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('INITIAL_NODE_POSITIONS — 모든 NODE_IDS에 좌표 존재', () => {
|
it('INITIAL_NODE_POSITIONS — 모든 NODE_IDS에 좌표 존재', () => {
|
||||||
@@ -20,8 +20,8 @@ describe('canvasLayout', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('EDGES — 16개, source/target이 모두 NODE_IDS 안에 존재', () => {
|
it('EDGES — 18개, source/target이 모두 NODE_IDS 안에 존재', () => {
|
||||||
expect(EDGES).toHaveLength(16);
|
expect(EDGES).toHaveLength(18);
|
||||||
const validIds = new Set(Object.values(NODE_IDS));
|
const validIds = new Set(Object.values(NODE_IDS));
|
||||||
for (const e of EDGES) {
|
for (const e of EDGES) {
|
||||||
expect(validIds.has(e.source)).toBe(true);
|
expect(validIds.has(e.source)).toBe(true);
|
||||||
@@ -30,10 +30,11 @@ describe('canvasLayout', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('EDGES — 7개 점수 노드는 모두 gate 입력 + combine 출력을 가짐', () => {
|
it('EDGES — 8개 점수 노드는 모두 gate 입력 + combine 출력을 가짐', () => {
|
||||||
const SCORE_IDS = [
|
const SCORE_IDS = [
|
||||||
NODE_IDS.FOREIGN, NODE_IDS.VOLUME, NODE_IDS.MOMENTUM,
|
NODE_IDS.FOREIGN, NODE_IDS.VOLUME, NODE_IDS.MOMENTUM,
|
||||||
NODE_IDS.HIGH52W, NODE_IDS.RS, NODE_IDS.MA, NODE_IDS.VCP,
|
NODE_IDS.HIGH52W, NODE_IDS.RS, NODE_IDS.MA, NODE_IDS.VCP,
|
||||||
|
NODE_IDS.AI_NEWS,
|
||||||
];
|
];
|
||||||
for (const sid of SCORE_IDS) {
|
for (const sid of SCORE_IDS) {
|
||||||
const hasGateInput = EDGES.some(
|
const hasGateInput = EDGES.some(
|
||||||
@@ -54,9 +55,10 @@ describe('canvasLayout', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('SCORE_NODE_NAME_MAP — 7개 점수 노드 ID → backend node name', () => {
|
it('SCORE_NODE_NAME_MAP — 8개 점수 노드 ID → backend node name', () => {
|
||||||
expect(Object.keys(SCORE_NODE_NAME_MAP)).toHaveLength(7);
|
expect(Object.keys(SCORE_NODE_NAME_MAP)).toHaveLength(8);
|
||||||
expect(SCORE_NODE_NAME_MAP[NODE_IDS.FOREIGN]).toBe('foreign_buy');
|
expect(SCORE_NODE_NAME_MAP[NODE_IDS.FOREIGN]).toBe('foreign_buy');
|
||||||
expect(SCORE_NODE_NAME_MAP[NODE_IDS.VOLUME]).toBe('volume_surge');
|
expect(SCORE_NODE_NAME_MAP[NODE_IDS.VOLUME]).toBe('volume_surge');
|
||||||
|
expect(SCORE_NODE_NAME_MAP[NODE_IDS.AI_NEWS]).toBe('ai_news');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user