v2.2.313~319: 보고 품질·조사 신뢰성 수술 + 네이버 쇼핑인사이트/검색어트렌드 연동

- 파트별 완성, 웹 실접속 증빙, 턴 의도 브리핑, 본문 불일치 검증, 하위 질문
  체크리스트, 사이트 심층 수집 (PATCHNOTES.md v2.2.313~318 참조)
- /naver-shopping, /naver-trend: 네이버 데이터랩 API 연동. compare/discover
  자동 판별, API 배치 상한 캡슐화, 카테고리 트리 fuzzy 매칭, 자격증명은
  Astra 설정 패널 SecretStorage 로 관리
This commit is contained in:
2026-08-05 18:20:32 +09:00
parent 7d6b8b509f
commit c79eda1862
51 changed files with 37435 additions and 89 deletions
+57
View File
@@ -0,0 +1,57 @@
/**
* [v2.2.318] 하위 질문 체크리스트 — "물음 3개에 하나도 직접 답하지 않는" 실사례 가드.
*/
import {
extractSubQuestions,
clauseKeyTokens,
buildSubQuestionBlock,
checkSubQuestionCoverage,
formatSubQuestionFooter,
} from '../src/intelligence/subQuestions';
const PROMPT = 'https://www.koritips.com 분석해줘. 어떠한 주제로 블로그가 운영되고 있는지, 글의 퀄리티는 어떠한지, 검색해서 들어온 사람이 글을 읽고 도움이 되었을지';
describe('extractSubQuestions', () => {
test('실사례 프롬프트에서 물음 3개 추출', () => {
const qs = extractSubQuestions(PROMPT);
expect(qs.length).toBe(3);
expect(qs[0]).toContain('주제');
expect(qs[1]).toContain('퀄리티');
expect(qs[2]).toContain('도움');
});
test('물음이 1개뿐이면 빈 목록 (본문 전체가 답)', () => {
expect(extractSubQuestions('이 코드가 왜 느린지 알려줘')).toEqual([]);
expect(extractSubQuestions('안녕하세요')).toEqual([]);
});
});
describe('clauseKeyTokens / coverage', () => {
test('특징 토큰에서 조사·의문사 제거', () => {
const keys = clauseKeyTokens('글의 퀄리티는 어떠한지');
expect(keys).toContain('퀄리티');
expect(keys).not.toContain('어떠한');
});
test('실사례 답변(평가 형용사만) → 퀄리티·도움 물음 누락 감지', () => {
const answer = 'koritips.com은 기술 아카이브로서의 가치가 매우 높습니다. 신뢰성과 지속 가능성, 타겟 명확성 측면에서 높은 평가를 받을 수 있습니다.';
const cov = checkSubQuestionCoverage(PROMPT, answer);
expect(cov.total).toBe(3);
expect(cov.missing.length).toBeGreaterThanOrEqual(2);
expect(formatSubQuestionFooter(cov)).toContain('미답변 물음');
});
test('각 물음에 답한 답변은 통과', () => {
const answer = '주제는 블로그 운영 팁입니다. 글의 퀄리티는 단계별 스크린샷이 있어 높습니다. 검색 유입자에게 실질적 도움이 됩니다.';
expect(checkSubQuestionCoverage(PROMPT, answer).missing).toEqual([]);
});
});
describe('buildSubQuestionBlock', () => {
test('물음 2개 이상이면 체크리스트 블록 생성', () => {
const blk = buildSubQuestionBlock(PROMPT);
expect(blk).toContain('[하위 질문 체크리스트');
expect(blk).toContain('1.');
expect(blk).toContain('구체 근거');
});
test('단일 질문은 빈 문자열', () => {
expect(buildSubQuestionBlock('서버 상태 확인해줘')).toBe('');
});
});