
    (<i;                         d Z ddlZddlmZ ddlZej
                  j                  d e ee      j                  j                               ddl
mZmZmZ  G d d      ZdZdZ G d	 d
      Z G d d      Zy)uL  
test_report_utils.py

scripts/report_utils.py 단위 테스트 (TDD RED 단계)

테스트 항목:
1. SCQA 형식 보고서 - **A** 부분 정확 추출
2. SCQA 형식 - A 뒤에 **C** 등 다른 Bold 패턴에서 끊김
3. SCQA 형식 - A 뒤에 --- 에서 끊김
4. 비SCQA 형식 - 첫 ## 섹션 본문 추출
5. 아무 패턴도 없는 경우 - 첫 10줄 반환
6. 파일 없는 경우 - None 반환
7. 빈 파일 - None 또는 빈 문자열 처리
8. 500자 초과 truncation
9. max_chars 커스텀 값
10. **A.** 패턴 (마침표 포함)
11. 멀티라인 A 내용
12. 존재하지 않는 경로 → 에러 없이 None 반환
13. extract_report_metadata - test_summary, files_count, unresolved, team_id, duration 추출
14. format_notification_message - 알림 메시지 포맷
15. extract_report_summary max_chars 기본값 1000 변경
    N)Path)extract_report_metadataextract_report_summaryformat_notification_messagec                      e Zd ZdZdeddfdZdeddfdZdeddfdZdeddfdZdeddfd	Z	deddfd
Z
deddfdZdeddfdZdeddfdZdeddfdZdeddfdZdeddfdZdeddfdZdeddfdZdeddfdZdeddfdZdeddfdZdeddfdZdeddfdZdeddfdZdeddfdZdeddfdZdeddfdZdeddfdZdeddfdZdeddfdZy)TestExtractReportSummaryu-   report_utils.extract_report_summary 테스트tmp_pathreturnNc                 ^    |dz  }|j                  dd       t        |      }|J d|v sJ y)u>   **A**: 패턴에서 A 내용을 정확히 추출해야 한다.	report.mdu   **S**: 상황 설명

**C**: 복잡한 문제 발생

**Q**: 어떻게 해결할까?

**A**: 로그 분석 도구를 도입하여 에러 추적을 자동화했다.
utf-8encodingNu   로그 분석 도구
write_textr   selfr	   reportresults       P/home/jay/workspace/.worktrees/task-2057-dev2/scripts/tests/test_report_utils.py!test_scqa_a_colon_extracts_answerz:TestExtractReportSummary.test_scqa_a_colon_extracts_answer*   sP    K'^  	 	
 (/!!!%///    c                 j    |dz  }|j                  dd       t        |      }|J d|v sJ d|vsJ y)uJ   **A**: 내용 이후 **Q**: 등 다른 Bold 패턴에서 끊겨야 한다.r   uS   **A**: 내용은 여기까지입니다.

**Q**: 이건 포함되면 안 됩니다.
r   r   Nu    내용은 여기까지입니다."   이건 포함되면 안 됩니다.r   r   s       r   &test_scqa_a_stops_at_next_bold_patternz?TestExtractReportSummary.test_scqa_a_stops_at_next_bold_pattern>   sY    K'g 	 	

 (/!!!1V;;;36AAAr   c                 j    |dz  }|j                  dd       t        |      }|J d|v sJ d|vsJ y)u:   **A**: 내용 이후 --- 구분선에서 끊겨야 한다.r   u]   **A**: 해결책은 캐시를 적용하는 것이다.
---
이건 포함되면 안 됩니다.
r   r   Nu.   해결책은 캐시를 적용하는 것이다.r   r   r   s       r   $test_scqa_a_stops_at_horizontal_rulez=TestExtractReportSummary.test_scqa_a_stops_at_horizontal_ruleP   sY    K't 	 	

 (/!!!?6III36AAAr   c                 ^    |dz  }|j                  dd       t        |      }|J d|v sJ y)uC   SCQA 패턴 없을 때 첫 ## 섹션 본문을 추출해야 한다.r   ua   # 작업 보고서

## 작업 내용

버그를 수정했습니다.

## 결과

테스트 통과
r   r   Nu   버그를 수정했습니다.r   r   s       r   )test_non_scqa_extracts_first_section_bodyzBTestExtractReportSummary.test_non_scqa_extracts_first_section_bodyb   sN    K' E 	 	

 (/!!!.&888r   c                 j    |dz  }|j                  dd       t        |      }|J d|v sJ d|vsJ y)uY   첫 ## 섹션 본문 추출 시 다음 ## 섹션 내용은 포함되지 않아야 한다.r   uy   ## 요약

데이터베이스 연결 문제를 해결했습니다.

## 상세 내용

이건 포함되면 안 됩니다.
r   r   Nu7   데이터베이스 연결 문제를 해결했습니다.r   r   r   s       r   0test_non_scqa_section_body_excludes_next_sectionzITestExtractReportSummary.test_non_scqa_section_body_excludes_next_sectiono   s\    K' X 	 	

 (/!!!HFRRR36AAAr   c                     t        d      D cg c]  }d|dz    d }}|dz  }|j                  dj                  |      d       t        |      }|J d
|v sJ d|v sJ d|vsJ y	c c}w )uT   SCQA도 ## 섹션도 없는 플레인 텍스트는 첫 10줄을 반환해야 한다.      줄    u    : 작업 내용 설명입니다.r   
r   r   Nu   줄 1:u   줄 10:u   줄 11:)ranger   joinr   )r   r	   ilinesr   r   s         r   &test_no_pattern_returns_first_10_linesz?TestExtractReportSummary.test_no_pattern_returns_first_10_lines   s    GLRyQ!4!u<=QQK'$))E*W='/!!!6!!!F"""&&& Rs   A)c                 j    |dz  }|j                  dd       t        |      }|J d|v sJ d|v sJ y)uI   10줄 미만 플레인 텍스트는 모든 내용을 반환해야 한다.r   u-   첫 번째 줄
두 번째 줄
세 번째 줄
r   r   Nu   첫 번째 줄u   세 번째 줄r   r   s       r   /test_no_pattern_fewer_than_10_lines_returns_allzHTestExtractReportSummary.test_no_pattern_fewer_than_10_lines_returns_all   sS    K'LW^_'/!!!6)))6)))r   c                 ,    |dz  }t        |      }|J y)uA   존재하지 않는 파일 경로는 None을 반환해야 한다.zno_such_file.mdNr   r   r	   missingr   s       r   "test_nonexistent_file_returns_nonez;TestExtractReportSummary.test_nonexistent_file_returns_none   s!    ..'0~~r   c                     |dz  }	 t        |      }|J y# t        $ r"}t        j                  d|        Y d}~yd}~ww xY w)uU   존재하지 않는 파일 경로 호출 시 예외가 발생하지 않아야 한다.zdoes_not_exist.mdN   예외가 발생했습니다: )r   	Exceptionpytestfailr   r	   r1   r   es        r   "test_nonexistent_file_no_exceptionz;TestExtractReportSummary.test_nonexistent_file_no_exception   sM    00	>+G4F>!> 	>KK8<==	>s    	A=Ac                 R    |dz  }|j                  dd       t        |      }|rJ y)uF   0 byte 빈 파일은 None 또는 빈 문자열을 반환해야 한다.empty.md r   r   Nr   r   r	   emptyr   s       r   %test_empty_file_returns_none_or_emptyz>TestExtractReportSummary.test_empty_file_returns_none_or_empty   s5    :%g.'. z6r   c                 ~    d}|dz  }|j                  d| dd       t        |      }|J t        |      dk  sJ y)	uO   1200자짜리 A 내용은 기본 max_chars(1000)자 이내로 잘려야 한다.u  가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가가r   **A**: r&   r   r   N  r   r   lenr   r	   long_contentr   r   s        r   /test_long_answer_truncated_to_default_max_charszHTestExtractReportSummary.test_long_answer_truncated_to_default_max_chars   sU    #K'GL>4wG'/!!!6{d"""r   c                 j    d}|dz  }|j                  d| dd       t        |      }|J ||v sJ y)u9   500자 이하 내용은 그대로 반환되어야 한다.u   짧은 해결 방법입니다.r   rB   r&   r   r   Nr   r   r	   contentr   r   s        r   test_short_answer_not_truncatedz8TestExtractReportSummary.test_short_answer_not_truncated   sQ    2K'GG9B/'B'/!!!&   r   c                     d}|dz  }|j                  d| dd       t        |d      }|J t        |      dk  sJ y	)
u;   max_chars=100 전달 시 100자 이내로 잘려야 한다.u  나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나나r   rB   r&   r   r   d   	max_charsNrD   rF   s        r   test_custom_max_chars_100z2TestExtractReportSummary.test_custom_max_chars_100   sW    "K'GL>4wG'#>!!!6{c!!!r   c                     d}|dz  }|j                  d| dd       t        |d      }|J t        |      dk  sJ y	)
u9   max_chars=50 전달 시 50자 이내로 잘려야 한다.uX  다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다다r   rB   r&   r   r   2   rO   NrD   rJ   s        r   test_custom_max_chars_50z1TestExtractReportSummary.test_custom_max_chars_50   sW    K'GG9B/'B'"=!!!6{b   r   c                     d}|dz  }|j                  d| dd       t        |d      }|J t        |      d
k\  sJ y	)uF   max_chars=1000 전달 시 600자 내용은 잘리지 않아야 한다.u  라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라라r   rB   r&   r   r   rC   rO   NX  rD   rJ   s        r   /test_custom_max_chars_1000_returns_full_contentzHTestExtractReportSummary.test_custom_max_chars_1000_returns_full_content   sW    K'GG9B/'B'$?!!!6{c!!!r   c                 ^    |dz  }|j                  dd       t        |      }|J d|v sJ y)uJ   **A.** 패턴(마침표 포함)도 정상적으로 추출되어야 한다.r   uO   **S.** 상황

**A.** 해결 방법은 인덱스를 추가하는 것입니다.
r   r   Nu8   해결 방법은 인덱스를 추가하는 것입니다.r   r   s       r   test_a_dot_pattern_extractedz5TestExtractReportSummary.test_a_dot_pattern_extracted  sK    K'c 	 	

 (/!!!IVSSSr   c                 j    |dz  }|j                  dd       t        |      }|J d|v sJ d|vsJ y)uN   **A.** 패턴 이후 **Q**: 등 다른 Bold 섹션 키에서 끊겨야 한다.r   uM   **A.** 이것이 정답입니다.

**Q**: 이건 포함되면 안 됩니다.
r   r   Nu   이것이 정답입니다.r   r   r   s       r   %test_a_dot_pattern_stops_at_next_boldz>TestExtractReportSummary.test_a_dot_pattern_stops_at_next_bold  s[    K' 	a 	 	

 (/!!!+v55536AAAr   c                 v    |dz  }|j                  dd       t        |      }|J d|v sJ d|v sJ d|v sJ y)	u[   **A**: 이후 멀티라인 내용이 다음 섹션 전까지 모두 포함되어야 한다.r   u>   **A**: 첫 줄
두번째 줄
세번째 줄

**다음 섹션**
r   r   Nu   첫 줄u   두번째 줄u   세번째 줄r   r   s       r   +test_multiline_a_content_all_lines_includedzDTestExtractReportSummary.test_multiline_a_content_all_lines_included#  sg    K'] 	 	

 (/!!!F"""&(((&(((r   c                 ^    |dz  }|j                  dd       t        |      }|J d|vsJ y)uY   멀티라인 A 내용 추출 시 다음 **Bold** 섹션은 포함되지 않아야 한다.r   uS   **A**: 첫 줄
두번째 줄

**다음 섹션**
이건 포함되면 안 됩니다.
r   r   Nr   r   r   s       r   &test_multiline_a_excludes_next_sectionz?TestExtractReportSummary.test_multiline_a_excludes_next_section2  sM    K' 	r 	 	

 (/!!!36AAAr   c                 2    |dz  dz  }t        |      }|J y)uN   존재하지 않는 .done 경로는 예외 없이 None을 반환해야 한다.reportsztask-999.doneNr/   )r   r	   missing_doner   s       r   0test_nonexistent_done_path_returns_none_no_errorzITestExtractReportSummary.test_nonexistent_done_path_returns_none_no_errorE  s%    )+o='5~~r   c                 >    |dz  dz  dz  dz  }t        |      }|J y)uK   중첩 경로의 존재하지 않는 파일도 None을 반환해야 한다.abcr   Nr/   r0   s       r   0test_deeply_nested_nonexistent_path_returns_nonezITestExtractReportSummary.test_deeply_nested_nonexistent_path_returns_noneM  s.    S.3&,{:'0~~r   c                 ^    |dz  }|j                  dd       t        |      }|J d|v sJ y)uK   **A** 패턴이 있으면 ## 섹션보다 우선하여 추출해야 한다.r   uV   ## 작업 내용

섹션 본문 내용입니다.

**A**: SCQA 답변 내용입니다.
r   r   Nu   SCQA 답변 내용입니다.r   r   s       r   %test_scqa_takes_priority_over_sectionz>TestExtractReportSummary.test_scqa_takes_priority_over_sectionY  sK    K'o 	 	

 (/!!!-777r   c                     dj                  t        d      D cg c]  }d| 	 c}      }|dz  }|j                  | dd       t        |      }|J d	|v sJ yc c}w )
uH   ## 섹션이 있으면 첫 10줄보다 우선하여 추출해야 한다.r&      r$   r   u,   

## 요약 섹션

섹션 본문입니다.
r   r   Nu   섹션 본문입니다.)r(   r'   r   r   )r   r	   r)   lines_beforer   r   s         r   /test_section_takes_priority_over_first_10_lineszHTestExtractReportSummary.test_section_takes_priority_over_first_10_linesf  s|    yyeAh!?D*!?@K'nTT 	 	

 (/!!!(F222 "@s   Ac                 j    |dz  }|j                  dd       t        |      }|J d|v sJ d|v sJ y)uI   한글 SCQA 보고서에서 A 내용이 정확히 추출되어야 한다.r   u/  **S**: 배포 이후 응답 속도가 현저히 느려졌습니다.

**C**: 데이터베이스 쿼리 최적화가 되지 않아 병목이 발생했습니다.

**Q**: 어떻게 성능을 개선할 수 있을까?

**A**: 인덱스 추가와 N+1 쿼리 제거로 응답 속도를 3배 개선했습니다.
r   r   Nu   인덱스 추가u   응답 속도를 3배 개선r   r   s       r   test_korean_content_in_scqaz4TestExtractReportSummary.test_korean_content_in_scqax  s^    K'j  	 	
 (/!!!!V+++-777r   c                 ^    |dz  }|j                  dd       t        |      }|J d|v sJ y)uL   한글 ## 섹션 보고서에서 본문이 정확히 추출되어야 한다.r   u   # 2026-03-24 작업 보고서

## 작업 요약

인증 모듈의 토큰 만료 처리 버그를 수정했습니다.
JWT 검증 로직을 개선하여 보안을 강화했습니다.

## 테스트 결과

모든 테스트 케이스 통과
r   r   NuC   인증 모듈의 토큰 만료 처리 버그를 수정했습니다.r   r   s       r   test_korean_content_in_sectionz7TestExtractReportSummary.test_korean_content_in_section  sP    K'2  	 	
 (/!!!TX^^^^r   c                 ~    d}|dz  }|j                  d| dd       t        |      }|J t        |      dk\  sJ y)	uQ   기본값이 1000으로 변경되어 600자 내용이 잘리지 않아야 한다.u  마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마마r   rB   r&   r   r   NrV   rD   rJ   s        r   test_default_max_chars_is_1000z7TestExtractReportSummary.test_default_max_chars_is_1000  sW    K'GG9B/'B (/!!!6{c!!!r   )__name__
__module____qualname____doc__r   r   r   r   r   r!   r+   r-   r2   r:   r@   rH   rL   rQ   rT   rW   rY   r[   r]   r_   rc   rh   rj   rn   rp   rr   rt    r   r   r   r   #   s   70$ 04 0(Bt B B$BT Bd B$9$ 94 9B BRV B$'t ' '	* 	*QU 	*4 D >4 >D >d t 	# 	#QU 	#	! 	! 	!	"$ 	"4 	"	! 	!$ 	!
" 
"QU 
" TT Td TBd Bt B()D )T )Bt B B& RV  RV 8d 8t 83 3QU 3$8D 8T 8"_t _ _,"t " "r   r   ui  # task-906.1 완료 보고서

**담당**: 오딘 (dev2-team 팀장)

---

## SCQA

**A**: auto_orch.py (591 LOC) + team_lock.py (75 LOC) 구현 완료. pytest 64건 전건 통과. pyright 0 에러.

---

## 생성/수정 파일 목록

- `orchestrator/auto_orch.py` — 메인 오케스트레이터
- `orchestrator/team_lock.py` — 팀별 뮤텍스
- `orchestrator/tests/test_phase3.py` — 테스트
- `~/.config/systemd/user/auto-orch.timer` — 타이머
- `~/.config/systemd/user/auto-orch.service` — 서비스
- `orchestrator/health.json` — 헬스 파일

---

## 테스트 결과 (정량적 증거)

**Phase 3 pytest**: 32/32 passed (0.45s)

**pyright**: 0 errors, 0 warnings

---

## 발견 이슈 및 해결

### 범위 외 미해결 (1건)

1. **qc_verify.py pyright 환경 이슈** — 범위 외 사유: qc_verify.py 자체 수정은 본 작업 범위 외
uO  # task-924.1 InfoKeyword 키워드 우선순위 평가 기능 추가

**담당**: 헤르메스 (dev1-team 팀장)
소요시간: 6분 12초

---

## SCQA

**A**: `prioritize_results()` 함수를 추가하여 백엔드에서 정렬 후 반환하도록 개선.
1. 정보성 키워드: 검색량 높은 순으로 정렬
2. 홍보성 키워드: `informational_count` 높은 순 → 동일 시 검색량 순
3. 각 결과에 `priority` + `priority_reason` 필드 추가
pytest 28건 통과, pyright 에러 0건

---

## 생성/수정 파일 목록

- `workers/info_keyword/prioritizer.py` — 우선순위 평가 함수
- `workers/info_keyword/models.py` — priority 필드 추가
- `workers/info_keyword/tests/test_prioritizer.py` — 테스트
- `workers/info_keyword/tests/test_contract.py` — 계약 테스트 갱신

---

## 발견 이슈 및 해결

### 자체 해결 (3건)
1. **black/isort 일부 불일치** — 재실행으로 해결
2. **total_blogs 미존재 가능성** — .get() 기본값 처리
3. **priority=0 falsy 문제** — 해당 없음 확인

### 범위 외 미해결 (0건)
없음
c                       e Zd ZdZdeddfdZdeddfdZdeddfdZdeddfdZdeddfd	Z	deddfd
Z
deddfdZdeddfdZdeddfdZdeddfdZdeddfdZdeddfdZdeddfdZdeddfdZdeddfdZdeddfdZdeddfdZy)TestExtractReportMetadatau.   report_utils.extract_report_metadata 테스트r	   r
   Nc                 |    |dz  }|j                  dd       t        |      }|d   J d|d   v sJ d|d   v sJ y)	uP   SCQA A 절에서 pytest 결과 패턴이 test_summary로 추출되어야 한다.r   u9   **A**: 모듈 구현 완료. pytest 64건 전건 통과.
r   r   test_summaryNr6   64r   r   r   s       r    test_test_summary_pytest_patternz:TestExtractReportMetadata.test_test_summary_pytest_pattern  sh    K'H 	 	

 )0n%1116.1111vn----r   c                 j    |dz  }|j                  dd       t        |      }|d   J d|d   v sJ y)uB   test_summary에 pyright 0 에러 결과가 포함되어야 한다.r   uD   **A**: 구현 완료. pytest 64건 전건 통과. pyright 0 에러.
r   r   r}   Npyrightr   r   s       r   "test_test_summary_includes_pyrightz<TestExtractReportMetadata.test_test_summary_includes_pyright  sU    K'S 	 	

 )0n%111F>2222r   c                 ^    |dz  }|j                  dd       t        |      }|d   dk(  sJ y)u_   '생성/수정 파일 목록' 섹션에서 bullet(-) 수를 files_count로 추출해야 한다.r   uq   ## 생성/수정 파일 목록

- `file_a.py` — 설명 A
- `file_b.py` — 설명 B
- `file_c.py` — 설명 C
r   r   files_count   Nr   r   s       r   !test_files_count_from_bullet_listz;TestExtractReportMetadata.test_files_count_from_bullet_list+  sH    K'+  	 	
 )0m$)))r   c                     |dz  }|j                  dd       t        |      }|d   dk(  sJ t        |d         dk(  sJ d|d   d	   v sJ y
)uc   '범위 외 미해결' 섹션의 항목들이 unresolved_items 리스트로 추출되어야 한다.r   um   ### 범위 외 미해결 (1건)

1. **qc_verify.py pyright 환경 이슈** — 범위 외 사유: 범위 외
r   r   unresolved_countr%   unresolved_items"   qc_verify.py pyright 환경 이슈r   Nr   r   rE   r   s       r   test_unresolved_items_extractedz9TestExtractReportMetadata.test_unresolved_items_extracted>  s{    K' B 	 	

 )0()Q...6,-.!3333v>P7QRS7TTTTr   c                 r    |dz  }|j                  dd       t        |      }|d   dk(  sJ |d   g k(  sJ y)	uc   '범위 외 미해결' 섹션이 없으면 unresolved_count=0, unresolved_items=[]이어야 한다.r   u:   ## 발견 이슈 및 해결

모든 이슈 해결 완료.
r   r   r   r   r   Nr   r   s       r   .test_no_unresolved_returns_zero_and_empty_listzHTestExtractReportMetadata.test_no_unresolved_returns_zero_and_empty_listP  sY    K'N 	 	

 )0()Q...()R///r   c                 j    |dz  }|j                  dd       t        |      }|d   J d|d   v sJ y)uR   '담당' 줄에서 괄호 안 'dev2-team' 형태 팀 ID가 추출되어야 한다.r   u&   **담당**: 오딘 (dev2-team 팀장)
r   r   team_idN	dev2-teamr   r   s       r   %test_team_id_extracted_korean_patternz?TestExtractReportMetadata.test_team_id_extracted_korean_patterna  sU    K'5 	 	

 )0i ,,,fY////r   c                 j    |dz  }|j                  dd       t        |      }|d   J d|d   v sJ y)u6   'team:' 패턴에서 팀 ID가 추출되어야 한다.r   u&   team: dev3-team

작업 내용 설명
r   r   r   Nz	dev3-teamr   r   s       r   &test_team_id_extracted_english_patternz@TestExtractReportMetadata.test_team_id_extracted_english_patternn  sU    K': 	 	

 )0i ,,,fY////r   c                 j    |dz  }|j                  dd       t        |      }|d   J d|d   v sJ y)u?   '소요시간' 패턴에서 duration이 추출되어야 한다.r   u)   소요시간: 12분 30초

작업 내용
r   r   durationNu   12분 30초r   r   s       r   test_duration_extractedz1TestExtractReportMetadata.test_duration_extracted  sU    K'= 	 	

 )0j!---z 2222r   c                 X    |dz  }|j                  dd       t        |      }|d   J y)uD   '소요시간' 패턴이 없으면 duration은 None이어야 한다.r   u(   ## 작업 내용

버그 수정 완료.
r   r   r   Nr   r   s       r   #test_duration_none_when_not_presentz=TestExtractReportMetadata.test_duration_none_when_not_present  sB    K'< 	 	

 )0j!)))r   c                     |dz  }t        |      }t        |t              sJ |d   J |d   
|d   dk(  sJ |d   dk(  sJ |d   g k(  sJ |d   J |d	   J y)
ub   존재하지 않는 파일은 모든 필드가 None/0/빈리스트인 dict를 반환해야 한다.no_such_report.mdr}   Nr   r   r   r   r   r   )r   
isinstancedictr0   s       r   ,test_nonexistent_file_returns_empty_metadatazFTestExtractReportMetadata.test_nonexistent_file_returns_empty_metadata  s    00(1&$'''n%---m$,}0E0JJJ()Q...()R///i (((j!)))r   c                     |dz  }|j                  dd       t        |      }t        |t              sJ |d   J |d   
|d   dk(  sJ |d	   dk(  sJ |d
   g k(  sJ |d   J |d   J y)uR   빈 파일은 모든 필드가 None/0/빈리스트인 dict를 반환해야 한다.r<   r=   r   r   r}   Nr   r   r   r   r   r   )r   r   r   r   r>   s       r   &test_empty_file_returns_empty_metadataz@TestExtractReportMetadata.test_empty_file_returns_empty_metadata  s    :%g.(/&$'''n%---m$,}0E0JJJ()Q...()R///i (((j!)))r   c                 *   |dz  }|j                  t        d       t        |      }t        |t              sJ |d   J d|d   v s	d|d   v sJ |d   d	k(  sJ |d
   dk(  sJ t        |d         dk(  sJ d|d   d   v sJ |d   J d|d   v sJ y)uH   task-906.1 형태 실제 보고서 전체 파싱이 정확해야 한다.task-906.1.mdr   r   r}   Nr6   r   r      r   r%   r   zqc_verify.pyr   r   r   )r   TASK_906_1_REPORTr   r   r   rE   r   s       r   test_task_906_1_full_parsingz6TestExtractReportMetadata.test_task_906_1_full_parsing  s    O++g>(0&$'''n%1116.11Y&BX5XXXm$)))()Q...6,-.!333(:!;A!>>>>i ,,,fY////r   c                 f    |dz  }|j                  t        d       t        |      }|d   dk(  sJ y)uV   보고서 첫 # 헤더에서 task ID 이후 텍스트를 title로 추출해야 한다.task-924.1.mdr   r   titleu7   InfoKeyword 키워드 우선순위 평가 기능 추가N)r   TASK_924_1_REPORTr   r   s       r   'test_title_extracted_from_first_headingzATestExtractReportMetadata.test_title_extracted_from_first_heading  s=    O++g>(0g"[[[[r   c                 X    |dz  }|j                  dd       t        |      }|d   J y)u4   # 헤더가 없으면 title은 None이어야 한다.zno_heading.mdu@   **담당**: 테스트 (dev1-team 팀장)

**A**: 작업 완료.
r   r   r   Nr   r   s       r   test_title_none_when_no_headingz9TestExtractReportMetadata.test_title_none_when_no_heading  sA    O+Q 	 	

 )0g&&&r   c                     |dz  }|j                  dd       t        |      }d|v sJ t        |d         dk(  sJ |d   d   d   d	k(  sJ |d   d   d
   dk(  sJ |d   d   d   dk(  sJ |d   d   d
   dk(  sJ y)u^   ### 자체 해결 섹션에서 이슈 목록이 구조화된 형태로 추출되어야 한다.z	issues.mdu   ### 자체 해결 (2건)
1. **black/isort 불일치** — 재실행으로 해결
2. **total_blogs 미존재** — .get() 기본값 처리
r   r   issues_resolved   r   summaryu   black/isort 불일치
resolutionu   재실행으로 해결r%   u   total_blogs 미존재u   .get() 기본값 처리Nr   r   s       r   test_issues_resolved_extractedz8TestExtractReportMetadata.test_issues_resolved_extracted  s    K'I 	 	 	
 )0 F***6+,-222'(+I6:QQQQ'(+L9=UUUU'(+I6:QQQQ'(+L9=VVVVr   c                 j    |dz  }|j                  dd       t        |      }d|v sJ |d   g k(  sJ y)uT   ### 자체 해결 섹션이 없으면 issues_resolved는 빈 리스트여야 한다.zno_resolved.mduN   **A**: 작업 완료.

## 생성/수정 파일 목록

- `file.py` — 설명
r   r   r   Nr   r   s       r   *test_issues_resolved_empty_when_no_sectionzDTestExtractReportMetadata.test_issues_resolved_empty_when_no_section  sT    ,,a 	 	

 )0 F***'(B...r   c                     |dz  }|j                  dd       t        |      }d|v sJ t        |d         dk(  sJ |d   d   d   d	k(  sJ d
|d   d   d   v sJ y)u^   ### 범위 외 미해결 섹션에서 이슈가 구조화된 형태로 추출되어야 한다.zunresolved.mduw   ### 범위 외 미해결 (1건)
1. **qc_verify.py pyright 환경 이슈** — 범위 외 사유: 본 작업 범위 외
r   r   issues_unresolvedr%   r   r   r   u
   범위 외r   Nr   r   s       r    test_issues_unresolved_extractedz:TestExtractReportMetadata.test_issues_unresolved_extracted  s    O+g 	 	
 )0"f,,,6-./1444)*1-i8<````v&9:1=lKKKKr   )ru   rv   rw   rx   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   ry   r   r   r{   r{     sU   8. .$ .$34 3D 3"*$ *4 *&U U U$0t 0PT 0"0d 0t 00t 0 0"3 3 3
*D 
*T 
* *T *d *$*t * *&0T 0d 02\ \ \
' 
' 
'Wt W W&/4 /D /L L$ Lr   r{   c                      e Zd ZdZdeddfdZdeddfdZdeddfdZdeddfdZdeddfd	Z	deddfd
Z
deddfdZdeddfdZdeddfdZdeddfdZdeddfdZdeddfdZdeddfdZdeddfdZdeddfdZdeddfdZdeddfdZdeddfdZdeddfdZdeddfdZdeddfdZdeddfdZdeddfdZy)TestFormatNotificationMessageu2   report_utils.format_notification_message 테스트r	   r
   Nc                 `    |dz  }|j                  t        d       t        d|      }d|v sJ y)u6   반환 메시지에 task_id가 포함되어야 한다.r   r   r   
task-906.1Nr   r   r   r   s       r   "test_basic_format_contains_task_idz@TestFormatNotificationMessage.test_basic_format_contains_task_id,  s:    O++g>,\6Bv%%%r   c                 |    |dz  }|j                  t        d       t        d|      }|J t        |      dkD  sJ y)uH   반환 메시지에 보고서 요약(summary)이 포함되어야 한다.r   r   r   r   Nr   r   r   r   rE   r   s       r   "test_basic_format_contains_summaryz@TestFormatNotificationMessage.test_basic_format_contains_summary5  sH    O++g>,\6B!!!6{Qr   c                     |dz  }|j                  t        d       t        d|      }d|v xs d|v }d|v xs
 d|v xs d	|v }|s|sJ y
y
)u`   반환 메시지에 메타데이터(파일 수, 테스트 결과 등)가 포함되어야 한다.r   r   r   r   6   파일r6   	   테스트r   Nr   )r   r	   r   r   	has_fileshas_tests         r   #test_basic_format_contains_metadatazATestFormatNotificationMessage.test_basic_format_contains_metadata?  sn    O++g>,\6B 6M7X%7	v%U)>U)vBUH$$Hyr   c                 n    |dz  }|j                  t        d       ddd}t        d||      }d|v sJ y	)
uF   done_data에 team_id가 있으면 메시지에 포함되어야 한다.r   r   r   r     r   duration_secondsr   	done_dataNr   r   r	   r   r   r   s        r   !test_done_data_team_id_in_messagez?TestFormatNotificationMessage.test_done_data_team_id_in_messageO  sF    O++g> +E	,\6YWf$$$r   c                     |dz  }|j                  t        d       ddd}t        d||      }d	|v sd
|v sd|v sJ yyy)uq   done_data에 duration_seconds가 있으면 사람이 읽기 좋은 형태로 메시지에 포함되어야 한다.r   r   r   r   r   r   r   r   u   12분750u   분Nr   r   s        r   *test_done_data_duration_seconds_in_messagezHTestFormatNotificationMessage.test_done_data_duration_seconds_in_messageY  s[    O++g> +E	,\6YW & EVOuFFO r   c                 :    |dz  }t        d|      }|J d|v sJ y)uT   보고서 파일이 없어도 예외 없이 기본 메시지를 반환해야 한다.r   
task-999.1N)r   r0   s       r   )test_missing_report_returns_basic_messagezGTestFormatNotificationMessage.test_missing_report_returns_basic_messageh  s3    00,\7C!!!v%%%r   c                     |dz  }	 t        d|      }|J y# t        $ r"}t        j                  d|        Y d}~yd}~ww xY w)uG   보고서 파일이 없을 때 예외가 발생하지 않아야 한다.r   r   Nr4   )r   r5   r6   r7   r8   s        r    test_missing_report_no_exceptionz>TestFormatNotificationMessage.test_missing_report_no_exceptionq  sQ    00	>0wGF%%% 	>KK8<==	>s    	A>Ac                     |dz  }|j                  t        d       ddd}t        d||      }t        |      d	k  sJ y
)u@   반환 메시지가 Telegram 4096자 제한 이내여야 한다.r   r   r   r   r   r   r   r      Nr   r   s        r   "test_message_within_telegram_limitz@TestFormatNotificationMessage.test_message_within_telegram_limit  sJ    O++g> +E	,\6YW6{d"""r   c                     ddz  }|dz  }|j                  d| ddj                  t        d      D cg c]  }d| d	
 c}      z   d
       t        d|      }t	        |      dk  sJ yc c}w )uH   매우 긴 보고서도 4096자 제한을 초과하지 않아야 한다.u   가i  zlong_report.mdrB   u"   

## 생성/수정 파일 목록

r=   rS   z- `file_u   .py` — 설명
r   r   ztask-long.1r   N)r   r(   r'   r   rE   )r   r	   rG   r   r)   r   s         r   .test_long_report_message_within_telegram_limitzLTestFormatNotificationMessage.test_long_report_message_within_telegram_limit  s    t|,,l^ $1 13577ejkmen;o`ahqcIZ<[;o3pq 	 	
 -]FC6{d""" <ps   A,c                     |dz  }|j                  t        d       	 t        d|d      }|J y# t        $ r"}t	        j
                  d|        Y d}~yd}~ww xY w)uJ   done_data=None이어도 예외 없이 메시지가 반환되어야 한다.r   r   r   r   Nr   r4   )r   r   r   r5   r6   r7   )r   r	   r   r   r9   s        r    test_none_done_data_no_exceptionz>TestFormatNotificationMessage.test_none_done_data_no_exception  sg    O++g>	>0vQUVF%%% 	>KK8<==	>s   1 	AAAc                 d    |dz  }|j                  t        d       t        d|d      }d|v sJ y)uG   done_data=None이어도 task_id가 메시지에 포함되어야 한다.r   r   r   r   Nr   r   r   s       r   $test_none_done_data_contains_task_idzBTestFormatNotificationMessage.test_none_done_data_contains_task_id  s<    O++g>,\6TRv%%%r   c                 j    |dz  }|j                  t        d       t        d|      }d|v sd|v sJ yy)un   미해결 이슈가 있으면 메시지에 경고 표시(⚠️ 또는 '미해결')가 포함되어야 한다.r   r   r   r      ⚠️u	   미해결Nr   r   s       r   (test_unresolved_issues_marked_in_messagezFTestFormatNotificationMessage.test_unresolved_issues_marked_in_message  sG    O++g>,\6B6![F%:::%:!r   c                 j    |dz  }|j                  t        d       t        d|      }d|v sd|v sJ yy)u>   미해결 건수(1건)가 메시지에 표시되어야 한다.r   r   r   r   u   1건1Nr   r   s       r   &test_unresolved_count_shown_in_messagezDTestFormatNotificationMessage.test_unresolved_count_shown_in_message  sE    O++g>,\6B3&=00=r   c                 t    |dz  }|j                  t        d       t        d|      }d|v sd|v sd|v sJ yyy)	uk   테스트 결과가 있으면 메시지에 🧪 표시 또는 '테스트' 문구가 포함되어야 한다.r   r   r   r      🧪r   r6   Nr   r   s       r   "test_test_result_marked_in_messagez@TestFormatNotificationMessage.test_test_result_marked_in_message  sQ    O++g>,\6B;&#8H<NNN<N#8r   c                 j    |dz  }|j                  t        d       t        d|      }d|v sd|v sJ yy)ub   파일 수가 있으면 메시지에 📁 표시 또는 '파일' 문구가 포함되어야 한다.r   r   r   r      📁r   Nr   r   s       r   "test_files_count_marked_in_messagez@TestFormatNotificationMessage.test_files_count_marked_in_message  sG    O++g>,\6B8v#555#5r   c                     |dz  }|j                  t        d       t        d|      }|j                  d      sJ d|v sJ y)u<   첫 줄이 **task-XXX.X 완료 보고** 형태여야 한다.r   r   r   
task-924.1z**task-u   완료 보고**N)r   r   r   
startswithr   s       r   test_new_format_bold_headerz9TestFormatNotificationMessage.test_new_format_bold_header  sN    O++g>,\6B  +++ F***r   c                     |dz  }|j                  t        d       t        d|      }|j                         }t	        |      dk\  sJ d|d   v sJ y)	uX   보고서 제목(task ID 제거 후 텍스트)이 2번째 줄에 포함되어야 한다.r   r   r   r   r   u   완료 보고서r%   N)r   r   r   
splitlinesrE   )r   r	   r   r   r*   s        r   test_new_format_includes_titlez<TestFormatNotificationMessage.test_new_format_includes_title  s\    O++g>,\6B!!#5zQ!U1X---r   c                 l    |dz  }|j                  t        d       ddi}t        d||      }d|v sJ y	)
ug   done_data에 duration_seconds가 있으면 '6분 12초' 형태로 제목 옆에 표시되어야 한다.r   r   r   r   t  r   r   u
   6분 12초Nr   r   r   r   s        r   !test_new_format_includes_durationz?TestFormatNotificationMessage.test_new_format_includes_duration  sF    O++g>'-	,\6YWv%%%r   c                 `    |dz  }|j                  t        d       t        d|      }d|v sJ y)u@   **핵심 결과** 섹션이 메시지에 포함되어야 한다.r   r   r   r   u   **핵심 결과**Nr   r   s       r   $test_new_format_core_results_sectionzBTestFormatNotificationMessage.test_new_format_core_results_section  s:    O++g>,\6B"f,,,r   c                 `    |dz  }|j                  t        d       t        d|      }d|v sJ y)ug   자체 해결 섹션이 있으면 **발견/해결 이슈 섹션이 메시지에 포함되어야 한다.r   r   r   r   u   **발견/해결 이슈Nr   r   s       r   test_new_format_issues_sectionz<TestFormatNotificationMessage.test_new_format_issues_section  s:    O++g>,\6B'6111r   c                     |dz  }|j                  t        d       t        d|      }d|vsJ d|vsJ d|vsJ d|vsJ d	|vsJ y
)uL   새 포맷에서 이모지(✅📁🧪⚠️📄)가 제거되어야 한다.r   r   r   r   u   ✅r   r   r   u   📄Nr   r   s       r   test_new_format_no_emojisz7TestFormatNotificationMessage.test_new_format_no_emojis  sr    O++g>,\6BF"""V###V###v%%%V###r   c                 `    |dz  }|j                  t        d       t        d|      }d|v sJ y)uM   핵심 결과가 번호 리스트(1. 2. 3.) 형태로 표시되어야 한다.r   r   r   r   z1.Nr   r   s       r   (test_new_format_numbered_list_in_resultszFTestFormatNotificationMessage.test_new_format_numbered_list_in_results!  s8    O++g>,\6Bv~~r   c                     |dz  }|j                  t        d       ddi}t        d||      }t        |      dk  sJ y	)
u=   새 포맷도 Telegram 4096자 제한을 준수해야 한다.r   r   r   r   r   r   r   r   N)r   r   r   rE   r   s        r   test_new_format_telegram_limitz<TestFormatNotificationMessage.test_new_format_telegram_limit*  sJ    O++g>'-	,\6YW6{d"""r   )ru   rv   rw   rx   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r  ry   r   r   r   r   %  s   <&4 &D &4 D 
%D 
%T 
% %$ %4 %	G4 	GD 	G&$ &4 &> >$ >#4 #D ##t #PT #$	> 	>$ 	>&T &d &; ;$ ;1t 1 1O4 OD O64 6D 6+D +T +	.t 	. 	.&$ &4 &-T -d -2t 2 2$$ $4 $ $ #t # #r   r   )rx   syspathlibr   r6   pathinsertstr__file__parentreport_utilsr   r   r   r   r   r   r{   r   ry   r   r   <module>r     s~   .    3tH~,,334 5 e eG" G"^% P$ N\L \LH	M# M#r   