From 353491b7ea7d41a1f54ccbcc941af29c779b9c53 Mon Sep 17 00:00:00 2001 From: SangwanYu Date: Mon, 1 Jun 2026 22:22:11 +0900 Subject: [PATCH 1/2] =?UTF-8?q?[improve/#69]=20=EC=A0=81=EC=9A=A9=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20timeline=20=EA=B7=BC=EA=B1=B0=20=EC=9D=98=EB=8F=84?= =?UTF-8?q?=20=EC=A0=95=EB=A0=AC=20=EA=B0=95=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../meeting_analysis/services/extraction.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/app/domains/meeting_analysis/services/extraction.py b/app/domains/meeting_analysis/services/extraction.py index d1e979b..b235ef9 100644 --- a/app/domains/meeting_analysis/services/extraction.py +++ b/app/domains/meeting_analysis/services/extraction.py @@ -92,6 +92,19 @@ '6. "개선하기로 함", "논의하기로 함", "준비하기로 함"처럼', " 대상/방법이 빠진 두루뭉술한 표현은 절대 사용하지 않는다.", ] +TIMELINE_EVIDENCE_ALIGNMENT_RULE = [ + "[타임라인 근거 정렬 규칙 - 반드시 준수]", + "1. timeline은 application_title/application_reasons의 핵심 작업 의도를", + " 직접 뒷받침하는 발화만 포함한다.", + "2. 같은 화면, API, 도메인명이 겹쳐도 작업 의도가 다르면", + " 같은 application의 timeline에 넣지 않는다.", + "3. '어디에 적용하는가'보다 '무엇을 바꾸기로 했는가'를 우선한다.", + '4. 예: "커밋 상세 페이지 로딩 스피너 컴포넌트 적용"에는', + ' "응답 필드명 통일" 발화를 포함하지 않는다.', + '5. 예: "커밋 상세 API 응답 필드명 통일"에는', + ' "로딩 스피너 적용" 발화를 포함하지 않는다.', + "6. 공통 범위 키워드만 겹치는 발화는 other_mentions로 보내거나 제외한다.", +] APPLICATION_POLICY_PROMPT = "\n".join( [ @@ -124,6 +137,7 @@ " member_id는 STT 데이터의 member_id 값을 사용하고, 없으면 null로 둔다.", "4. timeline의 content는 간략한 한 문장으로 작성한다.", *CONCRETE_APPLICATION_RULE, + *TIMELINE_EVIDENCE_ALIGNMENT_RULE, "------------------------------------------------------------", "", "[출력 JSON 구조]:", @@ -217,6 +231,7 @@ "3. member_id는 STT 데이터의 member_id 값을 사용하고, 없으면 null로 둔다.", "4. content는 간략한 한 문장으로 작성한다.", *CONCRETE_APPLICATION_RULE, + *TIMELINE_EVIDENCE_ALIGNMENT_RULE, "", "[출력 JSON 구조]", "{", From e94ab95ac61bb9d1f6e955c1b50d4ead5cd747a0 Mon Sep 17 00:00:00 2001 From: SangwanYu Date: Mon, 1 Jun 2026 22:23:35 +0900 Subject: [PATCH 2/2] =?UTF-8?q?[test/#69]=20timeline=20=EA=B7=BC=EA=B1=B0?= =?UTF-8?q?=20=EC=A0=95=EB=A0=AC=20=ED=94=84=EB=A1=AC=ED=94=84=ED=8A=B8=20?= =?UTF-8?q?=EA=B3=84=EC=95=BD=20=EA=B2=80=EC=A6=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_application_reason_format.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/test_application_reason_format.py b/tests/test_application_reason_format.py index ff57021..ec891a5 100644 --- a/tests/test_application_reason_format.py +++ b/tests/test_application_reason_format.py @@ -4,8 +4,11 @@ MeetingAnalysisResult, ) from app.domains.meeting_analysis.services.extraction import ( + APPLICATION_POLICY_PROMPT, + APPLICATIONS_ONLY_PROMPT, CONCRETE_APPLICATION_RULE, NOUN_ENDING_REASON_RULE, + TIMELINE_EVIDENCE_ALIGNMENT_RULE, _normalize_application_reason_outputs, _normalize_overall_reason_outputs, ) @@ -26,6 +29,21 @@ def test_application_prompt_rule_requires_concrete_action(self): assert "두루뭉술한 표현은 절대 사용하지 않는다" in joined_rule assert "WebSocket 발화로그 기준으로 STT 화자 보정 적용" in joined_rule + def test_timeline_prompt_rule_requires_intent_aligned_evidence(self): + joined_rule = "\n".join(TIMELINE_EVIDENCE_ALIGNMENT_RULE) + + assert "핵심 작업 의도" in joined_rule + assert "같은 화면, API, 도메인명이 겹쳐도" in joined_rule + assert "무엇을 바꾸기로 했는가" in joined_rule + assert "커밋 상세 페이지 로딩 스피너 컴포넌트 적용" in joined_rule + assert "응답 필드명 통일" in joined_rule + + def test_timeline_evidence_alignment_rule_is_used_by_analysis_prompts(self): + for prompt in (APPLICATION_POLICY_PROMPT, APPLICATIONS_ONLY_PROMPT): + assert "타임라인 근거 정렬 규칙" in prompt + assert "같은 application의 timeline에 넣지 않는다" in prompt + assert "공통 범위 키워드만 겹치는 발화" in prompt + def test_application_reasons_are_normalized_to_noun_endings(self): result = MeetingAnalysisResult( applications=[