feat(meet): 회의록 가이드 v2 반영 + 모델 출력 붕괴 복원력 (v2.2.253)
회의록 출력물 개선 (실무 회의록 가이드 v2): - 섹션 우선순위 재정렬(①결정 ②액션 ③오픈이슈 ④리스크 ⑤논의) - 논의사항 주제별 bullet 간결화, 오픈 이슈 섹션 복원 - 액션 아이템 산출물 컬럼 추가(담당·작업·기한·산출물 4요소), 담당자 개인 우선 - Executive Summary 결과 중심, 결정사항은 확정된 것만 모델 출력 붕괴(degeneration) 대응: - callLmSynthesis 재시도 내장(repeat_penalty↑/top_k↓로 반복 억제 강화) + looksDegenerate 감지 - 긴 녹취 조각 실패 시 절반 분할 재귀 재시도(12K→6K→3.5K) - 부분 회의록 fallback(한 조각 실패해도 전체 중단 안 함) 하위호환: 액션 표 파서 신6컬럼/구5컬럼 모두 파싱, 섹션 번호 무관 탐지, 회의일 추출 일시/날짜 둘 다 인식. 테스트 +13건(전체 659 통과). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,10 +4,12 @@
|
||||
* 과거 날짜는 등록하되 완료확인 표기, 기한 해석 불가 확정건은 보류(추측 등록 금지).
|
||||
*/
|
||||
import { classifyAction, parseConfirmArgs, normalizeDate, nextWeekday, taskKey } from '../src/features/datacollect/scheduling/meetRegistration';
|
||||
import { parseActionItems } from '../src/features/datacollect/scheduling/calendarHelpers';
|
||||
import { looksDegenerate } from '../src/features/datacollect/llm';
|
||||
|
||||
const MEET = new Date('2026-06-10');
|
||||
const TODAY = new Date('2026-06-11');
|
||||
const row = (due: string, status: string) => ({ owner: '나', work: '테스트 작업', detail: '', due, status });
|
||||
const row = (due: string, status: string) => ({ owner: '나', work: '테스트 작업', detail: '', deliverable: '', due, status });
|
||||
|
||||
describe('classifyAction — 등록 게이트 분기', () => {
|
||||
test('확정 + 명시 기한 → auto', () => {
|
||||
@@ -108,3 +110,51 @@ describe('보조 유틸', () => {
|
||||
expect(taskKey('DRM 라이선스 검토')).toBe(taskKey('drm 라이선스 검토'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseActionItems — 액션 표 파싱 (산출물 컬럼 + 하위호환)', () => {
|
||||
test('신 형식 6컬럼: 담당|작업|상세|산출물|기한|상태', () => {
|
||||
const report = [
|
||||
'## 4. 액션 아이템',
|
||||
'| 담당 | 작업 내용 | 작업 상세 | 산출물 | 기한 | 상태 |',
|
||||
'| --- | --- | --- | --- | --- | --- |',
|
||||
'| 송병준 | 테스트 샘플 3종 선정 | 후보 비교 | 테스트 URL 목록 | 6/18 | 확정 |',
|
||||
'',
|
||||
'## 5. 오픈 이슈',
|
||||
].join('\n');
|
||||
const rows = parseActionItems(report);
|
||||
expect(rows).toHaveLength(1);
|
||||
expect(rows[0]).toEqual({ owner: '송병준', work: '테스트 샘플 3종 선정', detail: '후보 비교', deliverable: '테스트 URL 목록', due: '6/18', status: '확정' });
|
||||
});
|
||||
|
||||
test('구 형식 5컬럼(산출물 없음)도 그대로 파싱 — deliverable 빈값', () => {
|
||||
const report = [
|
||||
'## 5. 액션 아이템',
|
||||
'| 담당 | 작업 내용 | 작업 상세 | 기한 | 상태 |',
|
||||
'| --- | --- | --- | --- | --- |',
|
||||
'| 나 | DRM 검토 | 상세 | 6/20 | 확정 |',
|
||||
].join('\n');
|
||||
const rows = parseActionItems(report);
|
||||
expect(rows).toHaveLength(1);
|
||||
expect(rows[0]).toMatchObject({ owner: '나', work: 'DRM 검토', deliverable: '', due: '6/20', status: '확정' });
|
||||
});
|
||||
|
||||
test('섹션 번호가 바뀌어도(번호 무관) 탐지', () => {
|
||||
const report = '## 9. 액션 아이템\n| 담당 | 작업 내용 | 기한 |\n| --- | --- | --- |\n| 나 | 작업 | 6/20 |';
|
||||
expect(parseActionItems(report)).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('looksDegenerate — 모델 출력 붕괴 감지', () => {
|
||||
test('정상 회의록은 통과', () => {
|
||||
expect(looksDegenerate('## 결정 사항\n- 테스트 샘플 3종 선정 — 근거: "샘플 3개로 가자"')).toBe(false);
|
||||
});
|
||||
test('구절 반복 루프 감지', () => {
|
||||
expect(looksDegenerate('서비스 기프트 서비스 기프트 서비스 기프트 서비스 기프트 서비스 기프트')).toBe(true);
|
||||
});
|
||||
test('대체문자(깨짐) 감지', () => {
|
||||
expect(looksDegenerate('정상 텍스트 �� 다략한서 량한서')).toBe(true);
|
||||
});
|
||||
test('표 구분선 반복은 오탐 아님', () => {
|
||||
expect(looksDegenerate('| --- | --- | --- | --- | --- | --- |')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user