6dc5f17dec
STT 화자번호(참석자 N) 박멸→회사 표준 팀/역할 귀속, 회의 헤더 전 청크 주입, 전역 헤드라인 추출, 결정 게이트·담화 상태 태깅, 슬림 6섹션 포맷, 타임스탬프 근거, parseActionItems 헤더명 기반 재작성, 검증 패스 5종. 전체 698 통과. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
76 lines
3.6 KiB
TypeScript
76 lines
3.6 KiB
TypeScript
/**
|
|
* Regression guard for the /meet prompt policy (v2.2.258).
|
|
*
|
|
* After a real meeting record review, the policy shifted to:
|
|
* 1) STT speaker numbers ("참석자 N") must NEVER appear in output — speakers are
|
|
* normalized to team/role instead (individual names only when certain).
|
|
* 2) empty cells are "—", not guessed placeholders; fully-undecided → open issue.
|
|
* 3) 근거 is a timestamp [mm:ss], not a raw STT quote.
|
|
* 4) decision vs action boundary: 일감→action table, 결정→pure direction only.
|
|
* 5) rejected/withdrawn hypotheses must not be promoted to issues/decisions.
|
|
*
|
|
* Prompts can't be unit-tested for model behavior, but we CAN guard that the
|
|
* policy text doesn't silently get dropped in a future refactor.
|
|
*/
|
|
|
|
import {
|
|
buildMeetPrompt,
|
|
buildMeetExtractPrompt,
|
|
buildMeetReducePrompt,
|
|
} from '../src/features/datacollect/prompts/meetPrompt';
|
|
|
|
const transcript = '참석자 1 00:01 이거는 이렇게 이렇게 해주세요. 참석자 2 00:10 네 수정할게요.';
|
|
const metadata = '회의명: 테스트';
|
|
|
|
describe('/meet prompt — speaker normalization & slim format policy', () => {
|
|
// The OUTPUT_FORMAT block is shared by the single-shot and reduce paths, so
|
|
// both must carry the policy.
|
|
const sharedFormatBuilders: Array<[string, string]> = [
|
|
['buildMeetPrompt', buildMeetPrompt(transcript, metadata)],
|
|
['buildMeetReducePrompt', buildMeetReducePrompt('## 액션\n- [넥서스개발팀] 작업', metadata)],
|
|
];
|
|
|
|
test.each(sharedFormatBuilders)('%s bans "참석자 N" tokens in output', (_name, prompt) => {
|
|
expect(prompt).toMatch(/"참석자 N"/);
|
|
expect(prompt).toMatch(/토큰 0개|절대 금지|절대 쓰지/);
|
|
});
|
|
|
|
test.each(sharedFormatBuilders)('%s attributes speakers by team/role', (_name, prompt) => {
|
|
expect(prompt).toMatch(/팀\/역할/);
|
|
});
|
|
|
|
test.each(sharedFormatBuilders)('%s requires timestamp 근거 ([mm:ss])', (_name, prompt) => {
|
|
expect(prompt).toMatch(/\[mm:ss\]/);
|
|
});
|
|
|
|
test.each(sharedFormatBuilders)('%s separates decision from action', (_name, prompt) => {
|
|
expect(prompt).toMatch(/결정 ↔ 액션 경계|순수 방향\/정책|결정\/액션 경계/);
|
|
});
|
|
|
|
test.each(sharedFormatBuilders)('%s blanks unknown cells with "—" instead of guessing', (_name, prompt) => {
|
|
expect(prompt).toMatch(/빈 칸은 "—"/);
|
|
});
|
|
|
|
test('extract (map) stage normalizes speakers to role, tags dialectic state & timestamps', () => {
|
|
const p = buildMeetExtractPrompt('참석자 1 00:01 이거 이렇게요', metadata, 1, 3);
|
|
expect(p).toMatch(/팀\/역할/); // role mapping
|
|
expect(p).toMatch(/\[mm:ss\]/); // timestamp grounding
|
|
expect(p).toMatch(/반박됨|철회됨/); // dialectic state tags
|
|
expect(p).toMatch(/안건\/주제/); // agenda coverage list
|
|
expect(p).toContain('[내용 확인필요]'); // content-empty deictic guard retained
|
|
});
|
|
|
|
test('reduce stage extracts a global headline & dedups across chunks', () => {
|
|
const p = buildMeetReducePrompt('## 액션\n- [기획] 작업', metadata);
|
|
expect(p).toMatch(/전역 헤드라인/);
|
|
expect(p).toMatch(/dedup|병합/);
|
|
expect(p).toMatch(/반박됨|폐기/); // rejected hypotheses not promoted
|
|
});
|
|
|
|
test('final checklist references the new gates', () => {
|
|
const prompt = buildMeetPrompt(transcript, metadata);
|
|
expect(prompt).toMatch(/"참석자 N" 토큰 0개/);
|
|
expect(prompt).toMatch(/빈 칸은 "—"/);
|
|
});
|
|
});
|