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:
@@ -31,9 +31,9 @@ export function toYmd(d: Date): string {
|
||||
return `${y}-${m}-${day}`;
|
||||
}
|
||||
|
||||
/** 회의록 본문의 "**날짜**: 2026년 05월 08일"에서 회의 날짜 추출. 없으면 fallback. */
|
||||
/** 회의록 본문의 "**일시**: 2026년 05월 08일"(구 형식 "**날짜**:" 도 호환)에서 회의 날짜 추출. 없으면 fallback. */
|
||||
export function extractMeetingDate(report: string, fallback: Date): Date {
|
||||
const m = report.match(/날짜\*{0,2}\s*[::]\s*\*{0,2}\s*(\d{4})\s*년\s*(\d{1,2})\s*월\s*(\d{1,2})\s*일/);
|
||||
const m = report.match(/(?:일시|날짜)\*{0,2}\s*[::]\s*\*{0,2}\s*(\d{4})\s*년\s*(\d{1,2})\s*월\s*(\d{1,2})\s*일/);
|
||||
if (m) {
|
||||
const d = new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
|
||||
if (!isNaN(d.getTime())) return d;
|
||||
@@ -75,11 +75,11 @@ export function resolveTaskDate(due: string, meetingDate: Date, today: Date): {
|
||||
* 5열 신표(담당 | 작업 내용 | 작업 상세 | 기한 | 상태) · 4열(상태 없음) ·
|
||||
* 구(舊) 3열 표(담당 | 작업 내용 | 기한)를 모두 지원한다. 누락 컬럼은 빈 문자열.
|
||||
*/
|
||||
export function parseActionItems(report: string): { owner: string; work: string; detail: string; due: string; status: string }[] {
|
||||
const rows: { owner: string; work: string; detail: string; due: string; status: string }[] = [];
|
||||
export function parseActionItems(report: string): { owner: string; work: string; detail: string; deliverable: string; due: string; status: string }[] {
|
||||
const rows: { owner: string; work: string; detail: string; deliverable: string; due: string; status: string }[] = [];
|
||||
let inSection = false;
|
||||
for (const line of report.split('\n')) {
|
||||
if (/^#{1,6}\s*5\.\s*액션\s*아이템/.test(line)) { inSection = true; continue; }
|
||||
if (/^#{1,6}\s*(?:\d+\.\s*)?액션\s*아이템/.test(line)) { inSection = true; continue; }
|
||||
if (!inSection) continue;
|
||||
if (/^#{1,6}\s/.test(line)) break; // 다음 섹션 시작 → 종료
|
||||
if (!/^\s*\|/.test(line)) continue;
|
||||
@@ -87,12 +87,16 @@ export function parseActionItems(report: string): { owner: string; work: string;
|
||||
if (cells.length < 3) continue;
|
||||
if (/^:?-+:?$/.test(cells[0])) continue; // 표 구분선
|
||||
if (cells[0] === '담당' || cells[1] === '작업 내용') continue; // 헤더
|
||||
if (cells.length >= 5) {
|
||||
rows.push({ owner: cells[0], work: cells[1], detail: cells[2], due: cells[3], status: cells[4] });
|
||||
if (cells.length >= 6) {
|
||||
// 신 형식: 담당 | 작업 내용 | 작업 상세 | 산출물 | 기한 | 상태
|
||||
rows.push({ owner: cells[0], work: cells[1], detail: cells[2], deliverable: cells[3], due: cells[4], status: cells[5] });
|
||||
} else if (cells.length === 5) {
|
||||
// 구 형식: 담당 | 작업 내용 | 작업 상세 | 기한 | 상태 (산출물 컬럼 없음)
|
||||
rows.push({ owner: cells[0], work: cells[1], detail: cells[2], deliverable: '', due: cells[3], status: cells[4] });
|
||||
} else if (cells.length === 4) {
|
||||
rows.push({ owner: cells[0], work: cells[1], detail: cells[2], due: cells[3], status: '' });
|
||||
rows.push({ owner: cells[0], work: cells[1], detail: cells[2], deliverable: '', due: cells[3], status: '' });
|
||||
} else {
|
||||
rows.push({ owner: cells[0], work: cells[1], detail: '', due: cells[2], status: '' });
|
||||
rows.push({ owner: cells[0], work: cells[1], detail: '', deliverable: '', due: cells[2], status: '' });
|
||||
}
|
||||
}
|
||||
return rows;
|
||||
|
||||
Reference in New Issue
Block a user