
    %~i                        d Z ddlZddlmc mZ ddlmZ ddl	m
Z
mZ h dZ G d d      Z G d d	      Z G d
 d      Z G d d      Z G d d      Z G d d      Z G d d      Z G d d      Z G d d      Z G d d      Z G d d      Z G d d      Z G d d      Z G d  d!      Z G d" d#      Z G d$ d%      Zy)&uH   
knowledge_extractor 단위 테스트
테스터: 모리건 (개발3팀)
    N)extract_knowledge)ChatMessageMessageType>   idtitleanswerexpertcategorykeywordsquestion
confidence
raw_threadsource_chatsource_datesubcategoryc                   $    e Zd Zd Zd Zd Zd Zy)TestThreadSplitByTagc           	         t        ddddt        j                        t        ddddt        j                        t        dd	dd
t        j                        g}t        |      }t	        |      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}}y )N
2025-12-0317:00   질문자/인카/서울\   #궁금증
• 유형 : 보상
• 질문내용 :
광응고술이 수술에 해당하나요?datetimeusercontenttype17:05   이해철/프라임/부산   네 수술에 해당합니다.17:06u4   약관이 개정되어서 명시되어 있습니다.   ==z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py6)slenresultpy0py1py3py6assert %(py8)spy8r   r   MESSAGEr   r'   
@pytest_ar_call_reprcompare@py_builtinslocals_should_repr_global_name	_safereprAssertionError_format_explanationselfmessagesr(   @py_assert2@py_assert5@py_assert4@py_format7@py_format9s           o/home/jay/projects/insuwiki/.worktrees/task-2064-dev6/scripts/kakao_knowledge/tests/test_knowledge_extractor.pytest_single_entry_createdz.TestThreadSplitByTag.test_single_entry_created#   s    !.y (( !18 (( !1N ((
. #8,6{a{a{ass66{a    c                    t        ddddt        j                        t        ddddt        j                        g}t        |      }|d	   d
   }d}||k(  }|slt	        j
                  d|fd||f      t	        j                  |      t	        j                  |      dz  }dd|iz  }t        t	        j                  |            d x}x}}y )Nr   r   r   r   r   r   r    r!   r   r
      보상r$   z%(py1)s == %(py4)sr+   py4assert %(py6)sr-   	r   r   r1   r   r2   r3   r7   r8   r9   r;   r<   r(   @py_assert0@py_assert3r=   @py_format5r@   s           rB      test_category_is_보상u,   TestThreadSplitByTag.test_category_is_보상>   s    !.y (( !18 ((
  #8,ay$00$0000$000$0000000000rD   c                    t        ddddt        j                        t        ddddt        j                        g}t        |      }d	}|d
   d   }||v }|slt	        j
                  d|fd||f      t	        j                  |      t	        j                  |      dz  }dd|iz  }t        t	        j                  |            d x}x}}y )Nr   r   r   r   r   r   r    r!   u   광응고술r   r   inz%(py1)s in %(py4)srH   rJ   r-   rK   rL   s           rB   #   test_question_contains_광응고술u8   TestThreadSplitByTag.test_question_contains_광응고술R   s    !.y (( !18 ((
  #8,6:!66~!66666~!6666~666!66666666rD   c                    t        ddddt        j                        t        ddddt        j                        g}t        |      }d	}|d
   d   }||v }|slt	        j
                  d|fd||f      t	        j                  |      t	        j                  |      dz  }dd|iz  }t        t	        j                  |            d x}x}}y )Nr   r   r   r   r   r   r    r!   u   수술에 해당r   r   rR   rT   rH   rJ   r-   rK   rL   s           rB   %   test_answer_contains_수술에_해당u:   TestThreadSplitByTag.test_answer_contains_수술에_해당f   s    !.y (( !18 ((
  #8,!8VAYx%88!%88888!%8888!888%88888888rD   N)__name__
__module____qualname__rC   rP   rU   rW    rD   rB   r   r   "   s     61(7(9rD   r   c                   $    e Zd Zd Zd Zd Zd Zy)TestThreadSplitBy30MinGapc           
          t        ddddt        j                        t        ddddt        j                        t        dd	d
dt        j                        t        ddddt        j                        gS )Nr   r   A/B/C&   #궁금증
• 유형 : 보상
질문1r   z17:10D/E/F   답변118:00G/H/I&   #궁금증
• 유형 : 약관
질문218:05   답변2r   r   r1   r;   s    rB   _make_messagesz(TestThreadSplitBy30MinGap._make_messages       !B (( !! (( !B (( !! ((/
 	
rD   c                 V   t        | j                               }t        |      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}}y 	N   r$   r&   r'   r(   r)   r.   r/   r   rj   r'   r2   r3   r4   r5   r6   r7   r8   r9   r;   r(   r=   r>   r?   r@   rA   s          rB   test_two_entries_createdz2TestThreadSplitBy30MinGap.test_two_entries_created       "4#6#6#896{a{a{ass66{arD   c                 <   t        | j                               }|d   d   }d}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}}y )	Nr   r
   rF   r$   rG   rH   rJ   r-   r   rj   r2   r3   r7   r8   r9   r;   r(   rM   rN   r=   rO   r@   s          rB   test_first_entry_categoryz3TestThreadSplitBy30MinGap.test_first_entry_category   k    "4#6#6#89ay$00$0000$000$0000000000rD   c                 <   t        | j                               }|d   d   }d}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}}y )	Nr#   r
      약관r$   rG   rH   rJ   r-   rt   ru   s          rB   test_second_entry_categoryz4TestThreadSplitBy30MinGap.test_second_entry_category   rw   rD   N)rX   rY   rZ   rj   rq   rv   rz   r[   rD   rB   r]   r]      s    
B 11rD   r]   c                   $    e Zd Zd Zd Zd Zd Zy)TestThreadSplitByDateChangec           
          t        ddddt        j                        t        ddddt        j                        t        d	d
ddt        j                        t        d	dddt        j                        gS )Nr   z23:50r_      #궁금증
질문r   z23:55ra      답변
2025-12-04z00:05rd   u   #궁금증
새 질문z00:10u
   새 답변rh   ri   s    rB   rj   z*TestThreadSplitByDateChange._make_messages   s    !, (( !  (( !0 (( !$ ((-
 	
rD   c                 V   t        | j                               }t        |      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}}y rm   ro   rp   s          rB   rq   z4TestThreadSplitByDateChange.test_two_entries_created   rr   rD   c                 <   t        | j                               }|d   d   }d}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}}y )	Nr   r   r   r$   rG   rH   rJ   r-   rt   ru   s          rB   test_first_entry_source_datez8TestThreadSplitByDateChange.test_first_entry_source_date   k    "4#6#6#89ay'7<7'<7777'<777'777<7777777rD   c                 <   t        | j                               }|d   d   }d}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}}y )	Nr#   r   r   r$   rG   rH   rJ   r-   rt   ru   s          rB   test_second_entry_source_datez9TestThreadSplitByDateChange.test_second_entry_source_date   r   rD   N)rX   rY   rZ   rj   rq   r   r   r[   rD   rB   r|   r|      s    
@ 88rD   r|   c                   $    e Zd Zd Zd Zd Zd Zy)TestNonMessageTypeExclusionc           
          t        ddddt        j                        t        ddddt        j                        t        dd	d
dt        j                        t        ddd
dt        j                        gS )Nr   r   r_   r~   r   17:02u   X님u   X님이 들어왔습니다.17:03ra      사진r      답변입니다.)r   r   r1   JOINPHOTOri   s    rB   rj   z*TestNonMessageTypeExclusion._make_messages   s    !, (( !5 %% !  && !* ((-
 	
rD   c                 V   t        | j                               }t        |      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}}y )	Nr#   r$   r&   r'   r(   r)   r.   r/   ro   rp   s          rB   test_one_entry_createdz2TestNonMessageTypeExclusion.test_one_entry_created  rr   rD   c                    t        | j                               }|d   d   }|D ]  }d}||v}|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd	|iz  }t        t        j                  |            d x}} y )
Nr   r   u   들어왔습니다not inz%(py1)s not in %(py3)sliner+   r,   assert %(py5)spy5)
r   rj   r2   r3   r7   r4   r5   r6   r8   r9   )r;   r(   rawr   rM   r=   @py_format4@py_format6s           rB   test_raw_thread_excludes_joinz9TestNonMessageTypeExclusion.test_raw_thread_excludes_join  s    "4#6#6#89Qi% 	4D'3't3333't333'333333t333t3333333	4rD   c                    t        | j                               }|d   d   }|D ]  }|j                  } |       }d}||k7  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      dz  }dd	|iz  }	t        t        j                  |	            d x}x}x}} y )
Nr   r   r   )!=)zD%(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.strip
}()
} != %(py7)sr   r*   py2rI   py7assert %(py9)spy9)r   rj   stripr2   r3   r4   r5   r6   r7   r8   r9   )
r;   r(   r   r   @py_assert1rN   @py_assert6r>   @py_format8@py_format10s
             rB   test_raw_thread_excludes_photoz:TestNonMessageTypeExclusion.test_raw_thread_excludes_photo  s    "4#6#6#89Qi% 	,D::+:<+8+<8++++<8++++++4+++4+++:+++<+++8+++++++	,rD   N)rX   rY   rZ   rj   r   r   r   r[   rD   rB   r   r      s    
@ 4,rD   r   c                   :    e Zd ZdedefdZd Zd Zd Zd Z	d Z
y	)
TestCategoryExtractionr   returnc                    t        ddd|t        j                        t        ddddt        j                        g}t        |      }t	        |      }d}||k(  }|st        j                  d	|fd
||f      dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}}|d   S )Nr   r   r_   r   r   ra   r   r#   r$   r&   r'   r(   r)   r.   r/   r   r0   )	r;   r   r<   r(   r=   r>   r?   r@   rA   s	            rB   _entry_for_contentz)TestCategoryExtraction._entry_for_content   s    ! (( !* ((
  #8,6{a{a{ass66{aayrD   c                 &   | j                  d      }|d   }d}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t	        t        j
                  |            d x}x}}y )	N%   #궁금증
• 유형 : 보상
질문r
   rF   r$   rG   rH   rJ   r-   r   r2   r3   r7   r8   r9   r;   entryrM   rN   r=   rO   r@   s          rB      test_category_보상u+   TestCategoryExtraction.test_category_보상5  e    ''(QRZ ,H, H,,,, H,,, ,,,H,,,,,,,rD   c                 &   | j                  d      }|d   }d}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t	        t        j
                  |            d x}x}}y )	Nu+   #궁금증
• 유형 : 고지의무
질문r
      고지의무r$   rG   rH   rJ   r-   r   r   s          rB      test_category_고지의무u1   TestCategoryExtraction.test_category_고지의무9  se    ''(WXZ 2N2 N2222 N222 222N2222222rD   c                 &   | j                  d      }|d   }d}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t	        t        j
                  |            d x}x}}y )	Nu%   #궁금증
• 유형 : 약관
질문r
   ry   r$   rG   rH   rJ   r-   r   r   s          rB      test_category_약관u+   TestCategoryExtraction.test_category_약관=  r   rD   c                 &   | j                  d      }|d   }d}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t	        t        j
                  |            d x}x}}y )	Nu%   #궁금증
• 유형 : 상품
질문r
   u   상품r$   rG   rH   rJ   r-   r   r   s          rB      test_category_상품u+   TestCategoryExtraction.test_category_상품A  r   rD   c                 &   | j                  d      }|d   }d}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t	        t        j
                  |            d x}x}}y )	Nu"   #궁금증
유형 없이 질문만r
   u   기타r$   rG   rH   rJ   r-   r   r   s          rB   !   test_category_기타_when_no_typeu8   TestCategoryExtraction.test_category_기타_when_no_typeE  se    ''(MNZ ,H, H,,,, H,,, ,,,H,,,,,,,rD   N)rX   rY   rZ   strdictr   r   r   r   r   r   r[   rD   rB   r   r     s.    # $ *-3---rD   r   c                       e Zd Zd Zy)TestExpertFieldc           
         t        ddddt        j                        t        ddddt        j                        t        dd	dd
t        j                        t        ddddt        j                        g}t        |      }|d   d   }d}||k(  }|slt	        j
                  d|fd||f      t	        j                  |      t	        j                  |      dz  }dd|iz  }t        t	        j                  |            d x}x}}y )Nr   r   r   r~   r   r   u   전문가A/프라임/부산rb   r"   rg   z17:07u   일반인B/KB/대전u   답변3r   r	   r$   rG   rH   rJ   r-   rK   rL   s           rB   &test_expert_is_most_frequent_responderz6TestExpertField.test_expert_is_most_frequent_responderP  s    !., (( !2! (( !2! (( !+! ((-
< #8,ay"C&CC"&CCCCC"&CCCC"CCC&CCCCCCCCrD   N)rX   rY   rZ   r   r[   rD   rB   r   r   O  s     DrD   r   c                       e Zd Zd Zy)TestPhoneNumberMaskingc                 X   t        ddddt        j                        t        ddddt        j                        g}t        |      }|d	   d
   }dj	                  |      }d}||v}|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            d x}}d}||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            d x}}y )Nr   r   r_   r~   r   r   ra   u+   문의는 010-1234-5678로 연락주세요.r   r   
z010-1234-5678r   r   	full_textr   r   r   z***-****-****rR   z%(py1)s in %(py3)sr   r   r1   r   joinr2   r3   r7   r4   r5   r6   r8   r9   )	r;   r<   r(   r   r   rM   r=   r   r   s	            rB   &test_phone_number_masked_in_raw_threadz=TestPhoneNumberMasking.test_phone_number_masked_in_raw_thready  s   !, (( !E ((
  #8,Qi%IIcN	/i////i/////////i///i///////+)++++)+++++++++)+++)+++++++rD   N)rX   rY   rZ   r   r[   rD   rB   r   r   x  s    ,rD   r   c                       e Zd Zd Zy)TestMinimumMessageCountc                 x   t        ddddt        j                        g}t        |      }t	        |      }d}||k(  }|st        j                  d|fd||f      d	t        j                         v st        j                  t              rt        j                  t              nd	d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}}y )Nr   r   r_   u)   #궁금증
질문만 있고 답변 없음r   r   r$   r&   r'   r(   r)   r.   r/   r0   r:   s           rB   #test_single_message_thread_excludedz;TestMinimumMessageCount.test_single_message_thread_excluded  s    !D ((
 #8,6{a{a{ass66{arD   N)rX   rY   rZ   r   r[   rD   rB   r   r     s     rD   r   c                   $    e Zd Zd Zd Zd Zd Zy)TestRequiredFieldsc                    t        ddddt        j                        t        ddddt        j                        g}t        |      }|D ]  }t        t        |j                               z
  }| }|s~t        j                  d	|       d
z   ddt        j                         v st        j                  |      rt        j                  |      ndiz  }t        t        j                  |            d } y )Nr   r   r_   r   r   r   ra   r   u   필수 키 누락: z
>assert not %(py0)sr*   missing)r   r   r1   r   REQUIRED_KEYSsetkeysr2   _format_assertmsgr4   r5   r6   r7   r8   r9   )r;   r<   r(   r   r   r   @py_format2s          rB   test_all_required_keys_presentz1TestRequiredFields.test_all_required_keys_present  s    !A (( !* ((
  #8, 	@E#c%**,&77G;?;??"5gY ???????w???w??????	@rD   c                    t        ddddt        j                        t        ddddt        j                        g}t        |      }|d	   d
   }t	        |t
              }|sddt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      dt        j                         v st        j                  t
              rt        j                  t
              ndt        j                  |      dz  }t        t        j                  |            d x}}y )Nr   r   r_   r~   r   r   ra   r   r   r   5assert %(py5)s
{%(py5)s = %(py0)s(%(py2)s, %(py3)s)
}
isinstancelistr*   r   r,   r   r   r   r1   r   r   r   r4   r5   r2   r6   r7   r8   r9   r;   r<   r(   r   r?   r   s         rB   test_keywords_is_listz(TestRequiredFields.test_keywords_is_list  s    !, (( !* ((
  #8, )J/6z/66666666z666z666/6666666666666666666rD   c                    t        ddddt        j                        t        ddddt        j                        g}t        |      }|d	   d
   }t	        |t
              }|sddt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      dt        j                         v st        j                  t
              rt        j                  t
              ndt        j                  |      dz  }t        t        j                  |            d x}}y )Nr   r   r_   r~   r   r   ra   r   r   r   r   r   r   r   r   r   s         rB   test_raw_thread_is_listz*TestRequiredFields.test_raw_thread_is_list  s    !, (( !* ((
  #8, )L18z1488888888z888z8881888888488848888888888rD   c                    t        ddddt        j                        t        ddddt        j                        g}t        |      }|d	   d
   }h d}||v }|slt	        j
                  d|fd||f      t	        j                  |      t	        j                  |      dz  }dd|iz  }t        t	        j                  |            d x}x}}y )Nr   r   r_   r~   r   r   ra   r   r   r   >   lowhighmediumrR   rT   rH   rJ   r-   rK   rL   s           rB   test_confidence_is_valid_valuez1TestRequiredFields.test_confidence_is_valid_value  s    !, (( !* ((
  #8,ay&C*CC&*CCCCC&*CCCC&CCC*CCCCCCCCrD   N)rX   rY   rZ   r   r   r   r   r[   rD   rB   r   r     s    @,7(9(DrD   r   c                       e Zd Zd Zd Zd Zy)TestUseLlmFalsec                    t        ddddt        j                        t        ddddt        j                        g}t        |d	
      }t	        |      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}y)u=   LLM 없이 규칙 기반으로 추출이 가능해야 한다.r   r   r_   u/   #궁금증
• 유형 : 보상
질문입니다.r   r   ra   r   Fuse_llmr#   r$   r&   r'   r(   r)   r.   r/   Nr0   r:   s           rB   &test_rule_based_extraction_without_llmz6TestUseLlmFalse.test_rule_based_extraction_without_llm  s     !K (( !* ((
" #8U;6{a{a{ass66{arD   c                    t        ddddt        j                        t        ddddt        j                        g}t        |d	
      }t	        |t
              }|sddt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      nddt        j                         v st        j                  t
              rt        j                  t
              ndt        j                  |      dz  }t        t        j                  |            d }y )Nr   r   r_   r~   r   r   ra   r   Fr   z5assert %(py4)s
{%(py4)s = %(py0)s(%(py1)s, %(py2)s)
}r   r(   r   )r*   r+   r   rI   r   )r;   r<   r(   rN   rO   s        rB   test_returns_listz!TestUseLlmFalse.test_returns_list  s    !, (( !  ((
  #8U;&$''''''''z'''z''''''&'''&''''''$'''$''''''''''rD   c                 j   t        g d      }g }||k(  }|st        j                  d|fd||f      dt        j                         v st        j
                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            d x}}y )	NFr   r$   )z%(py0)s == %(py3)sr(   r*   r,   r   r   )	r   r2   r3   r4   r5   r6   r7   r8   r9   )r;   r(   r=   r   r   r   s         rB   &test_empty_messages_returns_empty_listz6TestUseLlmFalse.test_empty_messages_returns_empty_list.  sf    "2u5v|vvvrD   N)rX   rY   rZ   r   r   r   r[   rD   rB   r   r     s     ,((rD   r   c                   $    e Zd Zd Zd Zd Zd Zy)TestIdFormatc           
          t        ddddt        j                        t        ddddt        j                        t        dd	d
dt        j                        t        ddddt        j                        gS )Nr   r   r_   r`   r   r   ra   rb   rc   rd   re   rf   rg   rh   ri   s    rB   _make_two_thread_messagesz&TestIdFormat._make_two_thread_messages9  rk   rD   c                 <   t        | j                               }|d   d   }d}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}}y )	Nr   r   z	kakao-001r$   rG   rH   rJ   r-   r   r   r2   r3   r7   r8   r9   ru   s          rB   test_first_entry_idz TestIdFormat.test_first_entry_idZ  g    "4#A#A#CDay-+-+----+------+-------rD   c                 <   t        | j                               }|d   d   }d}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}}y )	Nr#   r   z	kakao-002r$   rG   rH   rJ   r-   r   ru   s          rB   test_second_entry_idz!TestIdFormat.test_second_entry_id^  r   rD   c                 d   t        | j                               }|D ]  }|d   j                  d      }t        |      }d}||k(  }|st	        j
                  d|fd||f      dt        j                         v st	        j                  t              rt	        j                  t              nddt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      dz  }d	d
|iz  }t        t	        j                  |            dx}x}}|d   }	d}
|	|
k(  }|slt	        j
                  d|fd|	|
f      t	        j                  |	      t	        j                  |
      dz  }dd|iz  }t        t	        j                  |            dx}	x}}
|d   }	|	j                  } |       }|s`dt	        j                  |	      t	        j                  |      t	        j                  |      dz  }t        t	        j                  |            dx}	x}}|d   }t        |      }
d}|
|k(  }|st	        j
                  d|fd|
|f      dt        j                         v st	        j                  t              rt	        j                  t              ndt	        j                  |      t	        j                  |
      t	        j                  |      dz  }dd|iz  }t        t	        j                  |            dx}x}
x}} y)u+   id는 3자리 zero-padding이어야 한다.r   -rn   r$   r&   r'   partsr)   r.   r/   Nr   kakaorG   rH   rJ   r-   r#   zBassert %(py5)s
{%(py5)s = %(py3)s
{%(py3)s = %(py1)s.isdigit
}()
})r+   r,   r      )z0%(py4)s
{%(py4)s = %(py0)s(%(py2)s)
} == %(py7)sr   r   r   )r   r   splitr'   r2   r3   r4   r5   r6   r7   r8   r9   isdigit)r;   r(   r   r  r=   r>   r?   r@   rA   rM   rN   rO   r   r   r   r   r   s                    rB    test_id_zero_padded_three_digitsz-TestIdFormat.test_id_zero_padded_three_digitsb  s   "4#A#A#CD 	&E$K%%c*Eu:"":?""":""""""3"""3""""""u"""u""":""""""""""8&w&8w&&&&8w&&&8&&&w&&&&&&&8%8##%#%%%%%8%%%#%%%%%%%%%%Qx%3x=%A%=A%%%%=A%%%%%%3%%%3%%%x%%%=%%%A%%%%%%%	&rD   N)rX   rY   rZ   r   r   r   r  r[   rD   rB   r   r   8  s    
B..&rD   r   c                   "    e Zd ZdZd Zd Zd Zy)TestThreadSplitByQuestionTaguZ   #궁금증 태그 기반 스레드 분리: 시간 gap 없어도 태그마다 새 스레드c           
      &   t        ddddt        j                        t        ddddt        j                        t        dd	d
dt        j                        t        ddddt        j                        g}t        |      }t	        |      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}y)uC   같은 시간대에 #궁금증이 2번 나오면 2개 항목 생성r   r      A/인카/서울u/   #궁금증
• 유형 : 보상
질문1입니다r   r      B/프라임/부산u   답변1입니다r      C/KB/대전u/   #궁금증
• 유형 : 약관
질문2입니다r   u   답변2입니다rn   r$   r&   r'   r(   r)   r.   r/   Nr0   r:   s           rB   4test_two_question_tags_same_time_creates_two_entrieszQTestThreadSplitByQuestionTag.test_two_question_tags_same_time_creates_two_entriesu  s    \>O SZeZmZmo\>R 29L9LN\m SZeZmZmo\>R 29L9LN	
 #8,6{a{a{ass66{arD   c           
         t        ddddt        j                        t        ddddt        j                        t        dd	d
dt        j                        t        ddddt        j                        g}t        |      }|d   d   }d}||k(  }|slt	        j
                  d|fd||f      t	        j                  |      t	        j                  |      dz  }dd|iz  }t        t	        j                  |            d x}x}}y )Nr   r   r  r`   r   r   r  rb   r   r  ,   #궁금증
• 유형 : 고지의무
질문2r   rg   r   r
   rF   r$   rG   rH   rJ   r-   rK   rL   s           rB   $test_first_tag_gets_correct_categoryzATestThreadSplitByQuestionTag.test_first_tag_gets_correct_category  s    \>O JQ\QdQdf\>R )0C0CE\m PWbWjWjl\>R )0C0CE	
 #8,ay$00$0000$000$0000000000rD   c           
         t        ddddt        j                        t        ddddt        j                        t        dd	d
dt        j                        t        ddddt        j                        g}t        |      }|d   d   }d}||k(  }|slt	        j
                  d|fd||f      t	        j                  |      t	        j                  |      dz  }dd|iz  }t        t	        j                  |            d x}x}}y )Nr   r   r  r`   r   r   r  rb   r   r  r  r   rg   r#   r
   r   r$   rG   rH   rJ   r-   rK   rL   s           rB   %test_second_tag_gets_correct_categoryzBTestThreadSplitByQuestionTag.test_second_tag_gets_correct_category  s    \>O JQ\QdQdf\>R )0C0CE\m PWbWjWjl\>R )0C0CE	
 #8,ay$66$6666$666$6666666666rD   N)rX   rY   rZ   __doc__r  r  r  r[   rD   rB   r
  r
  r  s    d 17rD   r
  c                   "    e Zd ZdZd Zd Zd Zy) TestThreadSplitByQuestionPatternu,   질문 패턴 감지 기반 스레드 분리c           
      &   t        ddddt        j                        t        ddddt        j                        t        dd	d
dt        j                        t        ddddt        j                        g}t        |      }t	        |      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}}y )Nr   r   r  r`   r   r   r  rb   17:04r  u3   질문 드립니다. 실비 청구 관련해서요.r"   rg   rn   r$   r&   r'   r(   r)   r.   r/   r0   r:   s           rB   +   test_질문_드립니다_creates_new_threaduL   TestThreadSplitByQuestionPattern.test_질문_드립니다_creates_new_thread  s   \>O JQ\QdQdf\>R )0C0CE\m U\g\o\oq\>R )0C0CE	
 #8,6{a{a{ass66{arD   c           
      &   t        ddddt        j                        t        ddddt        j                        t        dd	d
dt        j                        t        ddddt        j                        g}t        |      }t	        |      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}}y )Nr   r   r  r`   r   r   r  rb   r  r  u2   문의 드립니다. 고지의무 관련입니다.r"   rg   rn   r$   r&   r'   r(   r)   r.   r/   r0   r:   s           rB   +   test_문의_드립니다_creates_new_threaduL   TestThreadSplitByQuestionPattern.test_문의_드립니다_creates_new_thread  s   \>O JQ\QdQdf\>R )0C0CE\m T[f[n[np\>R )0C0CE	
 #8,6{a{a{ass66{arD   c           
      &   t        ddddt        j                        t        ddddt        j                        t        dd	d
dt        j                        t        ddddt        j                        g}t        |      }t	        |      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}}y )Nr   r   r  r`   r   r   r  rb   r  r  u    약관 해석이 궁금합니다r"   rg   rn   r$   r&   r'   r(   r)   r.   r/   r0   r:   s           rB   '   test_궁금합니다_creates_new_threaduH   TestThreadSplitByQuestionPattern.test_궁금합니다_creates_new_thread  s   \>O JQ\QdQdf\>R )0C0CE\m BI\I\^\>R )0C0CE	
 #8,6{a{a{ass66{arD   N)rX   rY   rZ   r  r  r  r  r[   rD   rB   r  r    s    6   rD   r  c                       e Zd ZdZd Zd Zy)TestThreadSplitBy15MinGapu8   15분 gap 기반 스레드 분리 (30분→15분 축소)c           
      &   t        ddddt        j                        t        ddddt        j                        t        dd	d
dt        j                        t        ddddt        j                        g}t        |      }t	        |      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}y)u   16분 gap → 새 스레드r   r   r     #궁금증
질문1r   r   r  rb   z17:21r  u   #궁금증
질문2z17:25rg   rn   r$   r&   r'   r(   r)   r.   r/   Nr0   r:   s           rB   !test_16min_gap_creates_new_threadz;TestThreadSplitBy15MinGap.test_16min_gap_creates_new_thread  s    \>O 5K<O<OQ\>R )0C0CE\m 5K<O<OQ\>R )0C0CE	
 #8,6{a{a{ass66{arD   c           	         t        ddddt        j                        t        ddddt        j                        t        dd	dd
t        j                        g}t        |      }t	        |      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}y)u-   14분 gap → 같은 스레드 (15분 미만)r   r   r  r"  r   r   r  rb   z17:19u   추가 답변r#   r$   r&   r'   r(   r)   r.   r/   Nr0   r:   s           rB   test_14min_gap_same_threadz4TestThreadSplitBy15MinGap.test_14min_gap_same_thread  s     \>O 5K<O<OQ\>R )0C0CE\>R /k6I6IK
 #8,6{a{a{ass66{arD   N)rX   rY   rZ   r  r#  r%  r[   rD   rB   r   r     s    B  rD   r   c                   (    e Zd ZdZd Zd Zd Zd Zy)TestNoiseFilteringu   노이즈 메시지 필터링c           	      d   t        ddddt        j                        t        ddddt        j                        t        dd	d
dt        j                        g}t        |      }t	        |      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}dj                  |d   d         }d}	|	|v}|st        j                  d|fd|	|f      t        j                  |	      dt        j                         v st        j                  |      rt        j                  |      nddz  }
dd|
iz  }t        t        j                  |            dx}	}y)u2   단순 인사 메시지는 raw_thread에서 제외r   r   r     #궁금증
질문입니다r   17:01   X/GA/경기u   안녕하세요r   r     답변입니다r#   r$   r&   r'   r(   r)   r.   r/   N r   r   u   [X/GA/경기]r   r   	raw_textsr   r   r   )r   r   r1   r   r'   r2   r3   r4   r5   r6   r7   r8   r9   r   )r;   r<   r(   r=   r>   r?   r@   rA   r.  rM   r   r   s               rB   -test_simple_greeting_excluded_from_raw_threadz@TestNoiseFiltering.test_simple_greeting_excluded_from_raw_thread  sc    \>O =KDWDWY\m 18K8KM\>R 18K8KM
 #8,6{a{a{ass66{aHHVAY|45	/i////i/////////i///i///////rD   c           	      D   t        ddddt        j                        t        ddddt        j                        t        dd	d
dt        j                        g}t        |      }dj	                  |d   d         }d}||v}|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            dx}}y)u>   리액션 메시지(ㅋㅋ, ㅎㅎ)는 raw_thread에서 제외r   r   r  r)  r   r*  r+  u	   ㅋㅋㅋr   r  r,  r-  r   r   r   r   r.  r   r   r   Nr   r;   r<   r(   r.  rM   r=   r   r   s           rB   &test_reaction_excluded_from_raw_threadz9TestNoiseFiltering.test_reaction_excluded_from_raw_thread  s     \>O =KDWDWY\m ++2E2EG\>R 18K8KM
 #8,HHVAY|45	+{)++++{)+++{++++++)+++)+++++++rD   c           	         t        ddddt        j                        t        ddddt        j                        t        dd	dd
t        j                        g}t        |      }|d   d   D ]  }d|v s	g }d
}||v}|}|sd}||v }	|	}|sXt	        j
                  d|fd||f      t	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }
dd|
iz  }|j                  |       |st	        j
                  d	fd|f      t	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }dd|iz  }|j                  |       t	        j                  |d      i z  }dd|iz  }t        t	        j                  |            dx}x}x}x}x}}	 y)u-   감사합니다만 있는 메시지는 제외r   r   r  r)  r   r   r  r,  r   u   감사합니다r   r   u   질문r   )z%(py3)s not in %(py5)sr   )r,   r   z%(py7)sr   rR   )z%(py10)s in %(py12)s)py10py12z%(py14)spy14r#   zassert %(py17)spy17N)r   r   r1   r   r2   r3   r7   r4   r5   r6   append_format_boolopr8   r9   )r;   r<   r(   r   r   r=   r?   rM   @py_assert9@py_assert11r   r   @py_format13@py_format15@py_format16@py_format18s                   rB   "   test_감사합니다_only_excludedu5   TestNoiseFiltering.test_감사합니다_only_excluded  sS    \>O =KDWDWY\>R 18K8KM\>O 18K8KM
 #8,1Il+ 	ID D(H(H(4HHD8HHHHH(HHH(HHHHHHHHHHHHHHHHDHHHHHHHHHDHHHDHHHHHHHHHHHHHH	IrD   c                 
   t        ddddt        j                        t        ddddt        j                        g}t        |      }d	j	                  |d
   d         }d}||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            dx}}y)uJ   안녕하세요 + 질문이 같이 있는 메시지는 제거하지 않음r   r   r  r)  r   r   r  u0   안녕하세요 실비 관련 답변드립니다r-  r   r   u   실비 관련 답변rR   r   r.  r   r   r   Nr   r1  s           rB   4test_substantive_greeting_with_question_not_excludedzGTestNoiseFiltering.test_substantive_greeting_with_question_not_excluded0  s     \>O =KDWDWY\>R RYdYlYln
 #8,HHVAY|45	%2%2222%222%2222222222222222rD   N)rX   rY   rZ   r  r/  r2  r@  rB  r[   rD   rB   r'  r'    s    '0 ,I"
3rD   r'  c                       e Zd ZdZd Zd Zy)TestLlmPromptJsonParsingu.   LLM 정제 결과 JSON 파싱 테스트 (mock)c           
         ddl m} ddlm}m}  |t        ddddt        j                        t        dd	d
dt        j                        gdd      } |       } |       g|_        d|j                  d   _	         |       }||j                  j                  _         |||dd      }d}||u}	|	st        j                  d|	fd||f      dt        j                          v st        j"                  |      rt        j$                  |      ndt        j$                  |      dz  }
dd|
iz  }t'        t        j(                  |            dx}	}|d   }d}||k(  }|slt        j                  d|fd||f      t        j$                  |      t        j$                  |      dz  }dd|iz  }t'        t        j(                  |            dx}x}}|d    }d!}||k(  }|slt        j                  d|fd||f      t        j$                  |      t        j$                  |      dz  }dd|iz  }t'        t        j(                  |            dx}x}}|d"   }d#}||k(  }|slt        j                  d|fd||f      t        j$                  |      t        j$                  |      dz  }dd|iz  }t'        t        j(                  |            dx}x}}y)$u/   유효한 JSON 응답이 올바르게 파싱됨r   	MagicMock_analyze_thread_with_llmThreadr   r   r  u   질문입니다r   r   r  r,  2025-12-03 17:00Tr<   
start_timehas_question_tagu   {"title": "테스트 제목", "category": "보상", "question": "질문", "answer": "답변", "keywords": ["보상", "실비"], "confidence": "high", "is_noise": false}r#   	test_chatN)is not)z%(py0)s is not %(py3)sr(   r   r   r   r   u   테스트 제목r$   rG   rH   rJ   r-   r
   rF   r   r   unittest.mockrG  #kakao_knowledge.knowledge_extractorrI  rJ  r   r   r1   r   textr<   createreturn_valuer2   r3   r4   r5   r6   r7   r8   r9   )r;   rG  rI  rJ  threadmock_responsemock_clientr(   r=   r   r   r   rM   rN   rO   r@   s                   rB   test_valid_json_response_parsedz8TestLlmPromptJsonParsing.test_valid_json_response_parsedE  s   +X\>O 18K8KM\>R 18K8KM"

 )4A "!* )Sa %k3@##0)&+q+N!!vT!!!!vT!!!!!!v!!!v!!!T!!!!!!!g4"44"44444"4444444"44444444j!-X-!X----!X---!---X-------l#-v-#v----#v---#---v-------rD   c           
         ddl m} ddlm}m}  |t        ddddt        j                        t        dd	d
dt        j                        gdd      } |       } |       g|_        d|j                  d   _	         |       }||j                  j                  _         |||dd      }d}||u }	|	st        j                  d|	fd||f      dt        j                          v st        j"                  |      rt        j$                  |      ndt        j$                  |      dz  }
dd|
iz  }t'        t        j(                  |            dx}	}y)u/   is_noise: true 응답이 올바르게 감지됨r   rF  rH  r   r   r  u   안녕하세요~r   r   r  u   안녕하세요!rK  FrL  u-   {"is_noise": true, "reason": "단순 인사"}r#   rO  N)is)z%(py0)s is %(py3)sr(   r   r   r   rQ  )r;   rG  rI  rJ  rW  rX  rY  r(   r=   r   r   r   s               rB   test_noise_response_detectedz5TestLlmPromptJsonParsing.test_noise_response_detected^  s    +X\>O 29L9LN\>R 29L9LN"

 )5B "!*(Wa %k3@##0)&+q+Nv~vvvrD   N)rX   rY   rZ   r  rZ  r]  r[   rD   rB   rD  rD  B  s    8.2rD   rD  )r  builtinsr4   _pytest.assertion.rewrite	assertionrewriter2   rS  r   kakao_knowledge.modelsr   r   r   r   r]   r|   r   r   r   r   r   r   r   r   r
  r  r   r'  rD  r[   rD   rB   <module>rc     s      A ;*V9 V9|,1 ,1h+8 +8f0, 0,p(- (-`!D !DR, ,<   (QD QDr- -j2& 2&t,7 ,7h+  + f   J<3 <3H0 0rD   