a52bf6ee85
/stocks judge — 조건 판정 정확도 (P2/P3/P4):
- criteriaEval.ts 신설: 8개 키워드 중 수치 기준 7개("5,800%" 파싱·임계값
비교)와 충족/미충족 판정·투자성향별 대표 3개 선택을 코드로 결정론 계산.
LLM 은 '기술력' 도메인 정성 판단(키워드 모호 시)과 근거 서술만 담당,
실패 시 결정론 폴백 → judge 가 LLM 형식 오류로 실패하는 경로 제거.
- cmdJudge: 판정 전 Naver 실시간 펀더멘털 fetch(실패 시 저장값 폴백) +
결과에 데이터 출처 표기.
- tests/stocksCriteria.test.ts: 사용자 실제 분류 패턴(마녀공장/기가비스/
엔켐) 픽스처 8건 — 코드 판정이 기존 패턴과 일치함을 고정.
/meet — 할루시네이션·문맥 누락 (P1/P5/P6):
- 근거 인용 의무: 결정·액션마다 발언 원문 인용(근거: "…") — 인용 불가
항목은 결정/액션 금지 (날조 구조적 억제).
- 60K 하드 자르기 폐지 → 12K 조각 추출(Map) + 병합(Reduce) 2단계.
lost-in-the-middle·후반부 증발 해소, 커버리지 60K→144K자.
- g1nation.meetVerifyPass(기본 off): 결정·액션을 근거 소스와 LLM 대조해
확인 불가 항목을 '⚠️ 검증 결과' 섹션으로 표시.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
94 lines
4.5 KiB
TypeScript
94 lines
4.5 KiB
TypeScript
/**
|
|
* criteriaEval — `/stocks judge` 결정론 평가기 테스트.
|
|
* 픽스처는 옛 LLM 프롬프트에 명시돼 있던 사용자의 실제 분류 예시 3종
|
|
* (마녀공장/기가비스/엔켐) — 코드 판정이 사용자 패턴과 일치해야 한다.
|
|
*/
|
|
import { evaluateCriteria, buildVerdict, marketCapEok } from '../src/features/stocks/criteriaEval';
|
|
import type { Stock } from '../src/features/stocks/types';
|
|
|
|
const NOW = new Date('2026-06-10');
|
|
|
|
function judge(stock: Stock, techPass?: boolean) {
|
|
const ev = evaluateCriteria(stock, undefined, NOW);
|
|
return { ev, verdict: buildVerdict(ev, stock.투자성향, techPass) };
|
|
}
|
|
|
|
describe('stocks criteriaEval', () => {
|
|
test('마녀공장 패턴 — 충족 (ROE, 성장성, 유동성)', () => {
|
|
const { verdict } = judge({
|
|
이름: '마녀공장', 심볼: '439090', 투자성향: '스윙/중기',
|
|
'ROE(25E)': '15.6%', '영업이익률(25E)': '18.0%', 유보율: '5,800%',
|
|
PBR: '1.2', 시가총액: '4,000억',
|
|
});
|
|
expect(verdict.text).toBe('충족 (ROE, 성장성, 유동성)');
|
|
});
|
|
|
|
test('기가비스 패턴 — ROE 10% 미만이라 빠지고 수익성 개선 표기', () => {
|
|
const { verdict } = judge({
|
|
이름: '기가비스', 심볼: '420770', 투자성향: '스윙/중기',
|
|
'ROE(25E)': '7.23%', '영업이익률(25E)': '25.7%', 유보율: '4,250%',
|
|
상장일: '2024-05-24', PBR: '1.3', 시가총액: '3,000억',
|
|
});
|
|
// 통과: 성장성(영업이익률≥15), 유동성, 수익성(≥20→개선), PBR — ROE 는 미통과
|
|
expect(verdict.passed).not.toContain('ROE');
|
|
expect(verdict.text).toBe('충족 (성장성, 유동성, 수익성 개선)');
|
|
});
|
|
|
|
test('엔켐 패턴 — 통과 2개면 미충족', () => {
|
|
const { verdict } = judge({
|
|
이름: '엔켐', 심볼: '348370', 투자성향: '스윙/중기',
|
|
'ROE(25E)': '12.4%', '영업이익률(25E)': '8.5%', 유보율: '1,250%',
|
|
PBR: '3.5', 시가총액: '2조 1,000억',
|
|
});
|
|
// 통과: ROE, 유동성 (성장성·수익성·영업효율·PBR 미통과, 기술력은 먹거리 미입력→fail)
|
|
expect(verdict.passed.sort()).toEqual(['ROE', '유동성']);
|
|
expect(verdict.text).toMatch(/^미충족/);
|
|
});
|
|
|
|
test('저평가우량주 — PBR·ROE 우선 선택', () => {
|
|
const { verdict } = judge({
|
|
이름: '가상우량', 심볼: '000001', 투자성향: '저평가우량주',
|
|
'ROE(25E)': '11%', '영업이익률(25E)': '12%', 유보율: '3,500%',
|
|
PBR: '0.9', 시가총액: '6,000억',
|
|
});
|
|
// 통과: PBR, ROE, 유동성, 수익성, 안정성 → 우선순위로 PBR, ROE 먼저
|
|
expect(verdict.text).toBe('충족 (PBR, ROE, 수익성)');
|
|
});
|
|
|
|
test('기술력 — 키워드 명중 시 LLM 없이 통과', () => {
|
|
const { ev } = judge({
|
|
이름: '테크주', 심볼: '000002', 투자성향: '장기투자',
|
|
'ROE(25E)': '9%', '영업이익률(25E)': '16%', 유보율: '1,500%',
|
|
PBR: '2.5', '최대 먹거리': 'AI 반도체 설계',
|
|
});
|
|
const tech = ev.results.find(r => r.keyword === '기술력')!;
|
|
expect(tech.state).toBe('pass');
|
|
});
|
|
|
|
test('기술력 — 도메인 모호하면 llm 상태, techPass 반영', () => {
|
|
const stock: Stock = {
|
|
이름: '모호주', 심볼: '000003', 투자성향: '장기투자',
|
|
'ROE(25E)': '9%', '영업이익률(25E)': '16%', 유보율: '1,500%',
|
|
PBR: '2.5', '최대 먹거리': '프리미엄 화장품 ODM',
|
|
};
|
|
const ev = evaluateCriteria(stock, undefined, NOW);
|
|
expect(ev.results.find(r => r.keyword === '기술력')!.state).toBe('llm');
|
|
// techPass=true → 충족에 기여, false → 제외
|
|
const yes = buildVerdict(ev, '장기투자', true);
|
|
const no = buildVerdict(ev, '장기투자', false);
|
|
expect(yes.passed).toContain('기술력');
|
|
expect(no.passed).not.toContain('기술력');
|
|
});
|
|
|
|
test('데이터 없음(unknown)은 통과로 치지 않는다', () => {
|
|
const { verdict } = judge({ 이름: '빈데이터', 심볼: '000004' });
|
|
expect(verdict.text).toMatch(/^미충족/);
|
|
});
|
|
|
|
test('marketCapEok — 조/억 텍스트 파싱', () => {
|
|
expect(marketCapEok('5,000억')).toBe(5000);
|
|
expect(marketCapEok('1조 2,000억')).toBe(12000);
|
|
expect(marketCapEok('2조')).toBe(20000);
|
|
});
|
|
});
|