
    i+c                       d Z ddlmZ ddlZddlmc mZ ddl	Z	ddl
Z
ddlmZ ddlZddlZddlmZmZ e
j&                  j)                  d e ed                   ddlmZmZmZmZmZmZmZ dZd	Zd
Z dZ!e!fd%dZ"e!fd%dZ#e!fd%dZ$e!fd%dZ%e!fd%dZ&e!fd%dZ'e!fd%dZ(e!fd%dZ)e!fd%dZ*e!fd%dZ+d&dZ, G d d      Z- G d d      Z. G d d      Z/ G d d      Z0 G d d       Z1 G d! d"      Z2 G d# d$      Z3y)'ug  
test_quality_evaluator.py — task-2421 IDS Phase 1 단위 테스트
silent corruption (단조 그라데이션+박스+텍스트 1종 패턴) 영구 차단 검증

작성자: 카구야 (단위 테스트 담당)
관련 태스크: task-2421 (task-2389 한글 깨짐, task-2401 단조 그라데이션 silent pass 재발 방지)
             task-2421-G2 (로키 G2 적대적 평가 4건 약점 회귀 차단)

구현 의존: quality_evaluator.py (벤자이텐 작성)
  - check_visual_diversity: STD_THRESHOLD=25.0, UNIQUE_THRESHOLD=1000,
                             SPATIAL_COHERENCE_DIFF_MAX=25.0 (TV-static 차단)
  - check_brand_color_match: design_md["primary"] 키, Delta-E < 30,
                              BRAND_AREA_RATIO_MIN=0.10 (면적 비율 검증)
  - check_hybrid_pattern: 각 패턴별 알고리즘 (Sobel 기반)
  - check_font_size: pytesseract 미설치/예외 시 BLOCKED 통일 (passed=True, blocked=True, score=0)
  - check_ocr_confidence: pytesseract 없으면 BLOCKED, OCR_KOREAN_RATIO_MIN=0.5 (한글 비율 검증)
  - evaluate_image: 5개 check 통합, retry_hints = 각 check의 retry_hint 병합
    )annotationsN)Path)Image	ImageDrawz*/home/jay/workspace/skills/satori-cardnews)
EvalResultcheck_brand_color_matchcheck_font_sizecheck_hybrid_patterncheck_ocr_confidencecheck_visual_diversityevaluate_imagez0/home/jay/.local/share/fonts/Pretendard-Bold.otfz3/home/jay/.local/share/fonts/Pretendard-Regular.otfz./home/jay/.local/share/fonts/NotoSansCJKKR.otf)8  r   c                    t        j                  | d   | d   dft         j                        }t        | d         D ]%  }t	        dd|z  | d   z  z         }||dd|ddf<   ' t        j                  |      S )uX   단조 수직 회색 그라데이션 — silent corruption 대표 케이스 (task-2401).   r      dtype`   @   N)npzerosuint8rangeintr   	fromarray)sizearrxvs       A/home/jay/workspace/tests/skills/satori/test_quality_evaluator.py_make_monotone_gradientr!   :   sy    
((DGT!Wa(
9C47^ q(47223Aq!G ??3    c                0    t        j                  d| d      S )u8   단색 분홍 이미지 — std_mean 0, unique_colors 1.RGBz#FF69B4r   newr   s    r    _make_solid_pinkr(   C       99UD),,r"   c                    t        j                  d| d      }t        j                  |      }|j	                  g dddd       |j	                  g dd	       |S )
uA   흰색 배경 + 밝은 회색 박스만 — unique_colors 극소.r$   #FFFFFF)d   r,     r-   z#EEEEEEz#CCCCCC   )filloutlinewidth)   r2   p  r3   )r/   )r   r&   r   Draw	rectangle)r   imgdraws      r    _make_box_text_onlyr8   H   sN    
))E4
+C>>#DNN'iRSNTNN'iN8Jr"   c                    t         j                  j                  dd| d   | d   dft         j                        }t	        j
                  |      S )uF   랜덤 픽셀 노이즈 — Sobel edge density 0.9+ → h1_photo_card.r      r   r   r   )r   randomrandintr   r   r   )r   r   s     r    _make_random_noiser=   Q   sA    
))

AsT!Wd1gq$9

JC??3r"   c                   t        j                  | d   | d   dft         j                        }t        | d         D ]  }t        d| d   d      D ]  }|| d   z  || d   z  dz  z   dz  }dd	||z   d
z  z  dz  z   }t	        j
                  ||d      \  }}}t        |dz        ||||dz   df<   t        |dz        ||||dz   df<   t        |dz        ||||dz   df<     t        j                  |      S )u   
    HSV 전 색상 + 채도 변화 그라디언트 — unique_colors > 5000, 평균 채도 > 0.4.
    → h2_illustration_card.
    r   r   r   r      g      ?      ?g333333?g?
   g      $@g?r:   	r   r   r   r   colorsys
hsv_to_rgbr   r   r   )	r   r   yr   huesatrgbs	            r    _make_hsv_illustrationrK   W   s%   
 ((DGT!Wa(
9C47^ ,q$q'1% 	,AtAw;T!Ws!22c9CA|,t33C))#sC8GAq!"1s7|C1QqS5!"1s7|C1QqS5!"1s7|C1QqS5!	,, ??3r"   c                   d}t        j                  | d   | d   dft         j                        }d}t        d| d   |      D ]  }t        d| d   |      D ]  }|dz  dk(  r~|dz  dz  }t	        j
                  |d	d	      \  }}}	t        |d
z        ||||z   |||z   df<   t        |d
z        ||||z   |||z   df<   t        |	d
z        ||||z   |||z   df<   nd||||z   |||z   f<   |dz  }  t        j                  |      S )uw   
    100px 컬러 블록 + 흰 블록 교번 — edge_density 0.03~0.08, sat_std > 0.1.
    → h3_gpt_style_card.
    r,   r   r   r   r   r?   333333?r@   gffffff?r:      rB   )
r   blockr   idxrE   r   rF   rH   rI   rJ   s
             r    _make_gpt_style_blocksrQ   h   sP   
 E
((DGT!Wa(
9C
C1d1gu% 
q$q'5) 		AQw!|TzS("--c4>1a/21s7|AagIq5y!+,/21s7|AagIq5y!+,/21s7|AagIq5y!+,,/AagIq5y()1HC		
 ??3r"   c                   | \  }}t        j                  |      j                  dd      }t        j                  |      j                  dd      }t        j                  ||dft         j                        }d|z  |dz
  z  j                  t         j                        |dddddf<   d|z  |dz
  z  j                  t         j                        |dddddf<   d|ddddd	f<   t        j                  |      S )
u   
    2D RG 그라디언트 (R: x축, G: y축) — smoothness < 5 AND unique_colors >= 1000.
    → h4_gradient_card PASS, visual_diversity도 PASS (2D이므로 샘플링 다양성 확보).
    r   r   r   r:   Nr      r?   )r   arangereshaper   r   astyper   r   )r   why_idxx_idxr   s         r     _make_smooth_multicolor_gradientr\   ~   s    
 DAqIIaL  Q'EIIaL  B'E
((Aq!9BHH
-C%K1q5)11"((;C1aL%K1q5)11"((;C1aLC1aL??3r"   c                   t         j                  j                  dd| d   | d   dft         j                        j	                  t         j
                        }t         j                  j                  dd| d   | d   df      }t        j                  ||z   dd	      j	                  t         j                        }t        j                  |      S )
ut   
    JPEG-like 자연 노이즈 (1~15 범위 픽셀 차이) — noise_ratio > 0.15.
    → h5_user_photo_card.
    P      r   r   r   r   i(   r:   )	r   r;   r<   r   rW   int16clipr   r   )r   basenoiser   s       r    _make_jpeg_noisere      s    
 99RtAwQ&;288LSSTVT\T\]DIIc2Qa!'<=E
''$,3
'
.
.rxx
8C??3r"   c                   t        j                  | d   | d   dfg dt         j                        j                  t         j                        }t         j
                  j                  dd| d   | d   df      }t        j                  ||z   dd      j                  t         j                        }t        j                  |      S )	u6   Supabase primary #3ECF8E 청록색 dominant 이미지.r   r   r   >         r   i   r:   )
r   fullr   rW   ra   r;   r<   rb   r   r   )r   r   rd   s      r    _make_supabase_tealrm      s    
''47DGQ');288
L
S
STVT\T\
]CIIb!d1gtAw%:;E
''#+q#
&
-
-bhh
7C??3r"   c                0    t        j                  d| d      S )u+   완전 회색 — brand color 불일치용.r$   #888888r%   r'   s    r    _make_solid_grayrp      r)   r"   c                2    ||z  }| j                  |       |S )N)save)r6   tmp_pathnameps       r    _saverv      s    4AHHQKHr"   c                  "    e Zd ZdZd Zd Zd Zy)TestSilentCorruptionDetectionu?   A. silent corruption 검출 — task-2401 재발 방지 핵심.c                   t               }t        |      }|d   }d}||u }|st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }t        j
                  d|d    d|d	          d
z   d|iz  }t        t        j                  |            dx}x}}|d   }d}||u}|st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }t        j
                  d      d
z   d|iz  }t        t        j                  |            dx}x}}y)uo   
        A-1: 단조 회색 그라데이션 PNG → passed=False, reason에 'visual_diversity' 포함.
        passedFisz%(py1)s is %(py4)spy1py4uG   단조 그라데이션은 visual_diversity FAIL이어야 함. std_mean=std_meanz, unique_colors=unique_colors
>assert %(py6)spy6Nreasonis not)z%(py1)s is not %(py4)su'   FAIL 시 reason 필드가 있어야 함)r!   r   
@pytest_ar_call_reprcompare	_saferepr_format_assertmsgAssertionError_format_explanationselfr6   result@py_assert0@py_assert3@py_assert2@py_format5@py_format7s           r    2test_silent_corruption_monotone_gradient_box_failszPTestSilentCorruptionDetection.test_silent_corruption_monotone_gradient_box_fails   s    &'',h 	
5 	
5( 	
 	
5 	
 	
 
	   	
 	
 
	 $) 	
 	
 z*++;F?<S;TV	
 	
 	
 	
 	
 hVtVt+VVVtVVVVVVtVVV-VVVVVVVVr"   c                   t               }t        |      }|d   }d}||u }|st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }t        j
                  d|d          dz   d	|iz  }t        t        j                  |            d
x}x}}|d   }d}||k  }|st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }t        j
                  d|d          dz   d	|iz  }t        t        j                  |            d
x}x}}y
)uK   
        A-2: 단색 분홍 PNG → passed=False, std_mean 낮음.
        rz   Fr{   r}   r~   u>   단색 분홍은 visual_diversity FAIL이어야 함. std_mean=r   r   r   N      9@<z%(py1)s < %(py4)suQ   단색 이미지의 std_mean은 STD_THRESHOLD(25.0) 미만이어야 함. 실제: )r(   r   r   r   r   r   r   r   r   s           r    (test_silent_corruption_solid_color_failszFTestSilentCorruptionDetection.test_silent_corruption_solid_color_fails   sJ     ',h 	
5 	
5( 	
 	
5 	
 	
 		   	
 	
 		 $) 	
 	
  MVT^M_L`a	
 	
 	
 	
 	
 j! 	
D 	
!D( 	
 	
!D 	
 	
 		 " 	
 	
 		 %) 	
 	
  ``fgq`r_st	
 	
 	
 	
 	
 	
r"   c                   t               }t        |      }|d   }d}||k  }|st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }t        j
                  d|d          dz   d|iz  }t        t        j                  |            d	x}x}}|d
   }d}||u }|st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }t        j
                  d      dz   d|iz  }t        t        j                  |            d	x}x}}y	)u]   
        A-3: 단색 박스+텍스트만 PNG → unique_colors < 1000, passed=False.
        r     r   r   r~   uS   박스+텍스트만 있는 이미지는 unique_colors < 1000이어야 함. 실제: r   r   Nrz   Fr{   r}   uD   unique_colors < 1000 이미지는 visual_diversity FAIL이어야 함)r8   r   r   r   r   r   r   r   r   s           r    .test_silent_corruption_low_unique_colors_failszLTestSilentCorruptionDetection.test_silent_corruption_low_unique_colors_fails   s@    "#',o& 	
 	
&- 	
 	
& 	
 	
 
	 ' 	
 	
 
	 *. 	
 	
 o./1	
 	
 	
 	
 	
 h 	
5 	
5( 	
 	
5 	
 	
 		   	
 	
 		 $) 	
 	
  S	
 	
 	
 	
 	
 	
r"   N)__name__
__module____qualname____doc__r   r   r    r"   r    rx   rx      s    IW

r"   rx   c                  .    e Zd ZdZd Zd Zd Zd Zd Zy)TestHybridPatternDiversityub   B. 5 hybrid 패턴 분화 — 5가지 패턴이 각각 고유한 특징으로 구분됨을 검증.c                   t               }t        |d      }|d   }d}||u }|st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }t        j
                  d|d          d	z   d
|iz  }t        t        j                  |            dx}x}}|d   }d}||kD  }|st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }t        j
                  d|d          d	z   d
|iz  }t        t        j                  |            dx}x}}y)u   
        B-4: 랜덤 노이즈 PNG → Sobel edge density > 0.05 → h1_photo_card PASS.
        (실제 임계값: magnitude > 30 픽셀 비율 > 0.05)
        h1_photo_cardrz   Tr{   r}   r~   u?   랜덤 노이즈는 h1_photo_card PASS여야 함. edge_density=feature_valuer   r   Ng?>z%(py1)s > %(py4)su3   h1_photo_card: edge_density > 0.05 필요. 실제: )r=   r
   r   r   r   r   r   r   r   s           r    test_h1_photo_card_pattern_passz:TestHybridPatternDiversity.test_h1_photo_card_pattern_pass   sO   
 !"%c?;h 	
4 	
4' 	
 	
4 	
 	
 
	   	
 	
 
	 $( 	
 	
 "?346	
 	
 	
 	
 	
 o& 	
 	
&- 	
 	
& 	
 	
 		 ' 	
 	
 		 *. 	
 	
  B&BYAZ[	
 	
 	
 	
 	
 	
r"   c                   t               }t        |d      }|d   }d}||u }|st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }t        j
                  d|d    d	|d
          dz   d|iz  }t        t        j                  |            dx}x}}|d   }d}||kD  }|st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }t        j
                  d|d          dz   d|iz  }t        t        j                  |            dx}x}}y)u   
        B-5: HSV 전색상+채도변화 PNG → unique_colors > 5000, 평균채도 > 0.4
             → h2_illustration_card PASS.
        h2_illustration_cardrz   Tr{   r}   r~   uV   HSV 일러스트는 h2_illustration_card PASS여야 함. unique_colors(feature_value)=r   	, reason=r   r   r   Ni  r   r   u;   h2_illustration_card: unique_colors > 5000 필요. 실제: )rK   r
   r   r   r   r   r   r   r   s           r    !test_h2_illustration_pattern_passz<TestHybridPatternDiversity.test_h2_illustration_pattern_pass   s_   
 %&%c+ABh 	
4 	
4' 	
 	
4 	
 	
 
	   	
 	
 
	 $( 	
 	
 ,,2?,C+DIfU]N^M_a	
 	
 	
 	
 	
 o& 	
 	
&- 	
 	
& 	
 	
 		 ' 	
 	
 		 *. 	
 	
  J&Q`JaIbc	
 	
 	
 	
 	
 	
r"   c                   t               }t        |d      }|d   }d}||u }|st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }t        j
                  d|d    d	|d
          dz   d|iz  }t        t        j                  |            dx}x}}d}|d   }||k  }d}	||	k  }|r|st        j                  d||fd|||	f      t        j                  |      t        j                  |      t        j                  |	      dz  }
t        j
                  d|d          dz   d|
iz  }t        t        j                  |            dx}x}x}x}}	y)u   
        B-6: 100px 컬러+흰 블록 교번 PNG → edge_density 0.03~0.08, sat_std > 0.1
             → h3_gpt_style_card PASS.
        h3_gpt_style_cardrz   Tr{   r}   r~   uD   컬러+흰 블록은 h3_gpt_style_card PASS여야 함. edge_density=r   r   r   r   r   NgQ?g{Gz?)<=r   )z%(py1)s <= %(py5)sz%(py5)s <= %(py7)s)r   py5py7u+   h3: edge_density 0.03~0.08 필요. 실제: z
>assert %(py9)spy9)rQ   r
   r   r   r   r   r   r   )r   r6   r   r   r   r   r   r   @py_assert4@py_assert6@py_format8@py_format10s               r    test_h3_gpt_style_pattern_passz9TestHybridPatternDiversity.test_h3_gpt_style_pattern_pass  s   
 %&%c+>?h 	
4 	
4' 	
 	
4 	
 	
 
	   	
 	
 
	 $( 	
 	
 "?34IfX>N=OQ	
 	
 	
 	
 	
  	
vo. 	
t.6 	
$ 	
.$6 	
 	
 	
t.$ 	
 	
 		  	
 	
 		 / 	
 	
 		 37 	
 	
  :&:Q9RS	
 	
 	
 	
 	
 	
r"   c                   t               }t        |d      }t        |      }|d   }d}||u }|st        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }t        j                  d|d    d	|d
          dz   d|iz  }t        t        j                  |            dx}x}}|d   }d}||k\  }|st        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }t        j                  d|d          dz   d|iz  }t        t        j                  |            dx}x}}y)u   
        B-7: 부드러운 다색 선형 그라디언트 PNG → smoothness < 5
             → h4_gradient_card PASS.
        visual_diversity 와 별도 검사: multi-color 이므로 unique_colors >= 1000.
        h4_gradient_cardrz   Tr{   r}   r~   uS   부드러운 다색 그라디언트는 h4_gradient_card PASS여야 함. smoothness=r   r   r   r   r   Nr   r   >=z%(py1)s >= %(py4)su@   다색 그라디언트는 unique_colors >= 1000 필요. 실제: )	r\   r
   r   r   r   r   r   r   r   )	r   r6   	hp_result	vd_resultr   r   r   r   r   s	            r    test_h4_gradient_pattern_passz8TestHybridPatternDiversity.test_h4_gradient_pattern_pass  sh    /0(.@A	*3/	" 	
d 	
"d* 	
 	
"d 	
 	
 
	 # 	
 	
 
	 '+ 	
 	
 #O45Yy?R>SU	
 	
 	
 	
 	

 ) 	
T 	
)T1 	
 	
)T 	
 	
 		 * 	
 	
 		 .2 	
 	
  OyYhOiNjk	
 	
 	
 	
 	
 	
r"   c                   t               }t        |d      }|d   }d}||u }|st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }t        j
                  d|d          d	z   d
|iz  }t        t        j                  |            dx}x}}|d   }d}||kD  }|st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }t        j
                  d|d          d	z   d
|iz  }t        t        j                  |            dx}x}}y)ub   
        B-8: JPEG-like 노이즈 PNG → noise_ratio > 0.15 → h5_user_photo_card PASS.
        h5_user_photo_cardrz   Tr{   r}   r~   uG   JPEG noise 이미지는 h5_user_photo_card PASS여야 함. noise_ratio=r   r   r   NrM   r   r   u7   h5_user_photo_card: noise_ratio > 0.15 필요. 실제: )re   r
   r   r   r   r   r   r   r   s           r    test_h5_user_photo_pattern_passz:TestHybridPatternDiversity.test_h5_user_photo_pattern_pass'  sP     %c+?@h 	
4 	
4' 	
 	
4 	
 	
 
	   	
 	
 
	 $( 	
 	
 !/235	
 	
 	
 	
 	
 o& 	
 	
&- 	
 	
& 	
 	
 		 ' 	
 	
 		 *. 	
 	
  Ff_F]E^_	
 	
 	
 	
 	
 	
r"   N)	r   r   r   r   r   r   r   r   r   r   r"   r    r   r      s    l
 
 
 
&
r"   r   c                      e Zd ZdZd Zd Zy)TestBrandColorMatchuM   C. 브랜드 색상 매칭 — design_md["primary"] 키 사용, Delta-E < 30.c                   t               }ddi}t        ||      }|d   }d}||u }|st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }t        j
                  d|d	          d
z   d|iz  }t        t        j                  |            dx}x}}|d	   }d}||k  }|st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }t        j
                  d|d	          d
z   d|iz  }t        t        j                  |            dx}x}}y)ug   
        C-9: Supabase primary #3ECF8E(청록) dominant PNG → passed=True, min_delta_e < 30.
        primary#3ECF8Erz   Tr{   r}   r~   uT   Supabase 청록 dominant 이미지는 brand_color_match PASS여야 함. min_delta_e=min_delta_er   r   N      >@r   r   u/   min_delta_e는 30 미만이어야 함. 실제: )rm   r   r   r   r   r   r   r   	r   r6   	design_mdr   r   r   r   r   r   s	            r    )test_brand_color_match_supabase_teal_passz=TestBrandColorMatch.test_brand_color_match_supabase_teal_pass>  sX    "#	*	(i8h 	
4 	
4' 	
 	
4 	
 	
 
	   	
 	
 
	 $( 	
 	
 !-013	
 	
 	
 	
 	
 m$ 	
t 	
$t+ 	
 	
$t 	
 	
 		 % 	
 	
 		 (, 	
 	
  >f]>S=TU	
 	
 	
 	
 	
 	
r"   c                   t               }ddi}t        ||      }|d   }d}||u }|st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }t        j
                  d|d	          d
z   d|iz  }t        t        j                  |            dx}x}}|d	   }d}||kD  }|st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }t        j
                  d|d	          d
z   d|iz  }t        t        j                  |            dx}x}}y)uo   
        C-10: design_md primary=#3ECF8E(청록), PNG는 회색만 → passed=False, min_delta_e > 30.
        r   r   rz   Fr{   r}   r~   uP   청록 primary + 회색 PNG는 brand_color_match FAIL이어야 함. min_delta_e=r   r   r   Nr   r   r   u,   min_delta_e는 30 초과여야 함. 실제: )rp   r   r   r   r   r   r   r   r   s	            r    (test_brand_color_match_wrong_color_failsz<TestBrandColorMatch.test_brand_color_match_wrong_color_failsO  sX     	*	(i8h 	
5 	
5( 	
 	
5 	
 	
 
	   	
 	
 
	 $) 	
 	
 !-013	
 	
 	
 	
 	
 m$ 	
t 	
$t+ 	
 	
$t 	
 	
 		 % 	
 	
 		 (, 	
 	
  ;6-;P:QR	
 	
 	
 	
 	
 	
r"   N)r   r   r   r   r   r   r   r"   r    r   r   ;  s    W
"
r"   r   c                      e Zd ZdZd Zd Zy)TestOcrConfidenceu<   D. OCR confidence — task-2389 한글 깨짐 회귀 차단.c                d   	 ddl }t	        j
                  dt        d      }t        j                  |      }	 ddl	m
} |j                  t        d      }|j!                  dd	d
|       |j!                  ddd
|       t#        |      }|d   }d}	||	k\  }
|
st%        j&                  d|
fd||	f      t%        j(                  |      t%        j(                  |	      dz  }t%        j*                  d|d          dz   d|iz  }t-        t%        j.                  |            dx}x}
}	|d   }d}	||	u }
|
st%        j&                  d|
fd||	f      t%        j(                  |      t%        j(                  |	      dz  }t%        j*                  d      dz   d|iz  }t-        t%        j.                  |            dx}x}
}	y# t        $ r t        j                  d       Y w xY w# t        $ rG 	 ddl	m
} |j                  t        d      }n%# t        $ r ddl	m
} |j                         }Y nw xY wY w xY w)u   
        D-11: 한글 텍스트가 명확한 PNG → avg_confidence >= 70.
        pytesseract 없으면 skip (시스템 의존).
        r   Npytesseract not installedr$   r+   	ImageFontr^   )r,     u   안녕하세요 테스트#000000r/   font)r,   i  u   카드뉴스 품질 검사avg_confidenceF   r   r   r~   uC   명확한 한글 텍스트는 avg_confidence >= 70 필요. 실제: r   r   rz   Tr{   r}   u   OCR passed 필드 True 필요)pytesseractImportErrorpytestskipr   r&   SIZEr   r4   PILr   truetype	FONT_BOLD	Exception	FONT_NOTOload_defaulttextr   r   r   r   r   r   r   )r   rs   _ptr6   r7   r   r   r   r   r   r   r   r   s                r    $test_ocr_korean_high_confidence_passz6TestOcrConfidence.test_ocr_korean_high_confidence_passh  s   
	5% iitY/~~c"		0%%%i4D 			*9	PT	U		*:QU	V%c*&' 	
2 	
'2- 	
 	
'2 	
 	
 
	 ( 	
 	
 
	 ,. 	
 	
 ./02	
 	
 	
 	
 	
 hH4H4'HHH4HHHHHH4HHH)HHHHHHHH1  	5KK34	5  	00) )))R8 0) --/0		0sF   F: G :GG	H/)HH/H(%H/'H((H/.H/c                   	 ddl }t	        j
                  dt        d      }t        j                  |      }t        d      D ]%  }d|dz  z   }|j                  |d	|d
z   dgdd       ' t        |      }|d   d
k  }|d   dk(  }g }	|}
|s|}
|
sdddt        j                         v st        j                  |      rt        j                   |      ndiz  }|	j#                  |       |sXdddt        j                         v st        j                  |      rt        j                   |      ndiz  }|	j#                  |       t        j$                  |	d      i z  }t        j&                  d|d          dz   d|iz  }t)        t        j*                  |            dx}
}	y# t        $ r t        j                  d       Y w xY w)u   
        D-12: 깨진 문자(회색 사각형) PNG → avg_confidence < 70 또는 텍스트 미인식.
        pytesseract 없으면 skip.
        r   Nr   r$   r+   rk   r,   Z   r   r   i  z#AAAAAAro   )r/   r0   r           z%(py2)spy2is_low_confidencez%(py4)sr   
is_no_textr   u`   깨진 문자 이미지는 avg_confidence < 70 또는 no text여야 함. 실제: avg_confidence=
>assert %(py7)sr   )r   r   r   r   r   r&   r   r   r4   r   r5   r   @py_builtinslocalsr   _should_repr_global_namer   append_format_boolopr   r   r   )r   r   r6   r7   ir   r   r   r   @py_assert1r   @py_format3r   @py_format6r   s                  r    *test_ocr_garbled_text_low_confidence_failsz<TestOcrConfidence.test_ocr_garbled_text_low_confidence_fails  s   
	5% iitY/~~c"q 	UAa"fANNAsAFC0y)NT	U &c*"#34r9,-4
	
  	
 J 	
 	
 
6	
 	
  ! 	
 	
 
	 ! 	
 	
 
6	
 
 ! 	
 
6	
 	
  %/ 	
 	
 
	 %/ 	
 	
 
6	
 
	
 	
 	
 &&,-=&>%?A	
 	
 	
 	
 	
  	5KK34	5s   F   GGN)r   r   r   r   r   r   r   r"   r    r   r   e  s    FIB
r"   r   c                      e Zd ZdZd Zy)TestEvaluateImageIntegrationuB   E. 통합 evaluate_image — 5개 check 통합, EvalResult 반환.c                   t        j                  dd       t               }t        ||d      }ddi}t	        ||dt
              }t        |t              }|s!t        j                  d	      d
z   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}|j                   }d}	||	u }|st        j"                  d|fd||	f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |	      dz  }
t        j                  d|j$                   d|j&                         dz   d|
iz  }t        t        j                  |            dx}x}}	|j&                  }d}	||	k\  }|st        j"                  d|fd||	f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |	      dz  }
t        j                  d|j&                         dz   d|
iz  }t        t        j                  |            dx}x}}	y)u   
        E-13: 정상 카드뉴스 (랜덤 노이즈 h1_photo_card + #888888 brand)
               → passed=True, score >= 70.
        pytesseract 없으면 skip.
        r   r   r   znormal_card.pngr   z#808080r   png_pathr   hybrid_patterntarget_sizeu.   반환값은 EvalResult 인스턴스여야 함z7
>assert %(py4)s
{%(py4)s = %(py0)s(%(py1)s, %(py2)s)
}
isinstancer   r   )py0r   r   r   NTr{   z.%(py2)s
{%(py2)s = %(py0)s.passed
} is %(py5)sr  r   r   u;   정상 카드뉴스는 passed=True여야 함. fail_reasons=z, score=r   r   r   r   )z-%(py2)s
{%(py2)s = %(py0)s.score
} >= %(py5)su9   정상 카드뉴스는 score >= 70이어야 함. 실제: )r   importorskipr=   rv   r   r   r  r   r   r   r   r   r   r   r   r   rz   r   fail_reasonsscore)r   rs   r6   r  r   r   r   r   r   r   r   r   s               r    0test_evaluate_image_integration_normal_card_passzMTestEvaluateImageIntegration.test_evaluate_image_integration_normal_card_pass  s    	M2MN "h(9:	*	*	
 &*-_-__/_______z___z______&___&______*___*___-______}} 	
 	
}$ 	
 	
} 	
 	
 
6	
 	
   	
 	
 
	  	
 	
 
	  	
 	
 
	 !% 	
 	
 "//0H	
 	
 	
 	
 	
 || 	
r 	
|r! 	
 	
|r 	
 	
	6	
 	
   	
 	
 		  	
 	
 		  	
 	
 		  " 	
 	
  H~V	
 	
 	
 	
 	
 	
r"   N)r   r   r   r   r  r   r"   r    r   r     s
    L
r"   r   c                      e Zd ZdZd Zy)TestRetryHintsu<   F. retry_hints 검증 — FAIL 시 retry_hint 병합 반환.c                0   t               }t        ||d      }ddi}t        ||dt              }|j                  }d}||u }|st        j                  d|fd||f      d	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      t        j                  |      d
z  }	t        j                  d|j                         dz   d|	iz  }
t        t        j                  |
            dx}x}}|j                  }t!        |      }d}||kD  }|s-t        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                  |      t        j                  |      dz  }t        j                  d|j                         dz   d|iz  }t        t        j                  |            dx}x}x}}d|j                  v xs d|j                  v }|st        j                  d|j                         dz   ddt        j                         v st        j                  |      rt        j                  |      ndiz  }t        t        j                  |            y)u   
        F-14: 단조 그라데이션 PNG (visual_diversity FAIL)
               → retry_hints 비어있지 않음,
               force_pattern_diversity 또는 suggested_seed 포함.
        zmonotone_gradient.pngr   r   r   r  Fr{   r	  r   r
  u?   단조 그라데이션은 passed=False여야 함. fail_reasons=r   r   Nr   r   )zP%(py5)s
{%(py5)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.retry_hints
})
} > %(py8)slen)r  r   py3r   py8uB   FAIL 케이스는 retry_hints가 비어있으면 안 됨. 실제: z
>assert %(py10)spy10force_pattern_diversitysuggested_seeduM   retry_hints에 force_pattern_diversity 또는 suggested_seed 필요. 실제: z
>assert %(py0)sr  has_diversity_hint)r!   rv   r   r   rz   r   r   r   r   r   r   r   r  r   r   retry_hintsr  )r   rs   r6   r  r   r   r   r   r   r   r   r   @py_assert7r   @py_format9@py_format11r  @py_format1s                     r    test_fail_returns_retry_hintsz,TestRetryHints.test_fail_returns_retry_hints  s    &'h(?@	*	-	
 }} 	
 	
}% 	
 	
} 	
 	
	6	
 	
   	
 	
 		  	
 	
 		  	
 	
 		 !& 	
 	
  NfNaNaMbc	
 	
 	
 	
 	
 %% 	
s%& 	
 	
&* 	
 	
 	
& 	
 	
	6	
 	
   	
 	
 		  	
 	
	6	
 	
   	
 	
 		  	
 	
 		 & 	
 	
 		 ' 	
 	
 		 *+ 	
 	
  QQWQcQcPde	
 	
 	
 	
 	
 	
 &););; 66#5#55 	 " 	
 ))*,	
 	
 
6	
 	
  " 	
 	
 
	 " 	
 	
 	
 	
 	
!r"   N)r   r   r   r   r  r   r"   r    r  r    s
    F
r"   r  c                  (    e Zd ZdZd Zd Zd Zd Zy)TestLokiG2RegressionBlocku8   G. 로키 G2 적대적 평가 4건 약점 회귀 차단.c                   t         j                  j                  d       t        j                  ddt         j                        j                  t         j                        }|t         j                  j                  ddd      z  }t        j                  |dd      j                  t         j                        }t        j                  |      }t        |      }|d	   }d
}||u }|st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }t        j                  d|d    d|d          dz   d|iz  }t!        t        j"                  |            dx}x}}|d   }d}||kD  }|st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }t        j                  d|d          dz   d|iz  }t!        t        j"                  |            dx}x}}g }	|d   }d}
||
u}|}|rd}|d   }||v }|}|st        j                  d|fd||
f      t        j                  |      t        j                  |
      dz  }dd|iz  }|	j%                  |       |r_t        j                  d fd!f      t        j                  |      t        j                  |      d"z  }d#d$|iz  }|	j%                  |       t        j&                  |	d      i z  }t        j                  d%|d          d&z   d'|iz  }t!        t        j"                  |            dx}x}	x}x}x}
x}x}}y)(u   
        G-15 (CRITICAL): TV-static 노이즈 PNG → spatial_diff > 25.0 → passed=False.
        '공간 일관성' reason 포함 확인.
        *   r   r   r   rT   r   i<   r   r:   rz   Fr{   r}   r~   uH   TV-static 노이즈는 visual_diversity FAIL이어야 함. spatial_diff=spatial_diffz, std_mean=r   r   r   Nr   r   r   uX   TV-static의 spatial_diff는 SPATIAL_COHERENCE_DIFF_MAX(25.0) 초과여야 함. 실제: r   u   공간 일관성r   )z%(py3)s is not %(py6)s)r  r   z%(py8)sr  in)z%(py11)s in %(py14)s)py11py14z%(py16)spy16u9   FAIL reason에 '공간 일관성' 포함 필요. 실제: z
>assert %(py19)spy19)r   r;   seedrl   r   rW   ra   r<   rb   r   r   r   r   r   r   r   r   r   r   r   )r   r   r6   r   r   r   r   r   r   r   @py_assert5r   @py_assert10@py_assert13@py_assert12r  @py_format15@py_format17@py_format18@py_format20s                       r    'test_visual_diversity_tv_static_blockedzATestLokiG2RegressionBlock.test_visual_diversity_tv_static_blocked  s   
 			rggos"((;BB288Lryy  b/::ggc1c"))"((3ooc"',h 	
5 	
5( 	
 	
5 	
 	
 
	   	
 	
 
	 $) 	
 	
 ">23;vj?Q>RT	
 	
 	
 	
 	
 n% 	
 	
%, 	
 	
% 	
 	
 
	 & 	
 	
 
	 )- 	
 	
 n-.0	
 	
 	
 	
 	
	
vh 	
t 	
t+ 	
0B 	
fXFV 	
0BFV0V 	
 	
 	
t 	
 	
 		   	
 	
 		 (, 	
 	
 	
	6	
		
 	
0BFV 	
 	
 		 1C 	
 	
 		 GW 	
 	
 	
	6	
		
 	
 	
  HxHXGYZ	
 	
 	
 	
 	
 	
 	
r"   c                   t        j                  ddt         j                        }g d|ddddf<   t        j                  |      }ddd	}t        ||      }|d
   }d}||u }|st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }t        j                  d|d          dz   d|iz  }	t        t        j                  |	            dx}x}}|d   }d}||k  }|st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }t        j                  d|d          dz   d|iz  }	t        t        j                  |	            dx}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  }
t        j                  d      dz   d|
iz  }t        t        j                  |            dx}}y)ul   
        G-16 (MEDIUM): 99% 회색 + 1% 청록 PNG → matching_area_ratio < 0.10 → passed=False.
        r#  rT   r   rg   Nl   r   supabase)r   rt   rz   Fr{   r}   r~   uM   1% 청록 영역은 brand_color_match FAIL이어야 함. matching_area_ratio=matching_area_ratior   r   g?r   r   uO   matching_area_ratio는 BRAND_AREA_RATIO_MIN(0.10) 미만이어야 함. 실제: r&  )z%(py1)s in %(py3)sr   )r   r  u9   반환 dict에 'matching_area_ratio' 키가 있어야 함z
>assert %(py5)sr   )r   rl   r   r   r   r   r   r   r   r   r   r   r   r   r   )r   r   r6   r   r   r   r   r   r   r   @py_format4r   s               r    .test_brand_color_area_ratio_insufficient_failszHTestLokiG2RegressionBlock.test_brand_color_area_ratio_insufficient_fails  s/    ggos"((;(DSD$3$Jooc" ):>	(i8h 	
5 	
5( 	
 	
5 	
 	
 
	   	
 	
 
	 $) 	
 	
 ##)*?#@"AC	
 	
 	
 	
 	
 +, 	
t 	
,t3 	
 	
,t 	
 	
 
	 - 	
 	
 
	 04 	
 	
 3457	
 	
 	
 	
 	
 % 	
$. 	
 	
$ 	
 	
 		 % 	
 	
	6	
 	
  )/ 	
 	
 		 )/ 	
 	
  H	
 	
 	
 	
 	
r"   c                   ddl m} t        j                  dt        d      }|st        |t              }|d   }d}||u }|st        j                  d|fd||f      t        j                  |      t        j                  |      d	z  }t        j                  d
|d          dz   d|iz  }t        t        j                  |            dx}x}}|d   }d}||u }|st        j                  d|fd||f      t        j                  |      t        j                  |      d	z  }t        j                  d|j                  d             dz   d|iz  }t        t        j                  |            dx}x}}|d   }d}||k(  }|st        j                  d|fd||f      t        j                  |      t        j                  |      d	z  }t        j                  d|d          dz   d|iz  }t        t        j                  |            dx}x}}d}|d   }||v }|st        j                  d|fd||f      t        j                  |      t        j                  |      d	z  }t        j                  d|d          dz   d|iz  }t        t        j                  |            dx}x}}yt        j                  d       y)u   
        G-17 (HIGH): pytesseract 미설치 환경 → check_font_size가 BLOCKED 반환 확인.
        passed=True, blocked=True, score=0, reason에 'BLOCKED' 포함.
        r   )_TESSERACT_AVAILABLEr$   r+   rz   Tr{   r}   r~   u4   BLOCKED 상태에서 passed=True여야 함. 실제: r   r   Nblockedu5   BLOCKED 상태에서 blocked=True여야 함. 실제: r  )==)z%(py1)s == %(py4)su3   BLOCKED 상태에서 score=0이어야 함. 실제: BLOCKEDr   r&  )z%(py1)s in %(py4)su=   BLOCKED reason에 'BLOCKED' 문자열 포함 필요. 실제: u?   pytesseract installed — exception path not tested in this env)scripts.quality_evaluatorr=  r   r&   r   r	   r   r   r   r   r   r   getr   r   )	r   r=  r6   r   r   r   r   r   r   s	            r    *test_check_font_size_ocr_exception_blockedzDTestLokiG2RegressionBlock.test_check_font_size_ocr_exception_blocked&  s   
 	CiitY/#$S$/F(# t #t+  #t  I $  I (,    GvhGWFXY     )$  $,  $  I %  I )-    H

S\H]G^_     '? a ?a'  ?a  I #  I '(    FfWoEVW      x 0 9 00  9 0  I   I !1    PPVW_P`Oab      KKYZr"   c                   t        j                  dd       t        j                  dt        d      }t        j                  |      }	 ddlm} |j                  dd	      }|j                  ddd|       |j                  ddd|       |j                  ddd|       t        |      }|j                  dd      dk(  s|j                  d      dk(  r|d   }d}||u }|st!        j"                  d|fd||f      t!        j$                  |      t!        j$                  |      dz  }	t!        j&                  d      dz   d|	iz  }
t)        t!        j*                  |
            dx}x}}y|d   }d}||u }|st!        j"                  d|fd||f      t!        j$                  |      t!        j$                  |      dz  }	t!        j&                  d|j                  d        d!|j                  d"             dz   d|	iz  }
t)        t!        j*                  |
            dx}x}}g }|j                  }d"} ||      }d}||u}|}|rd#}|d"   }||v }|}|st!        j"                  d$|fd%||f      d&t-        j.                         v st!        j0                  |      rt!        j$                  |      nd&t!        j$                  |      t!        j$                  |      t!        j$                  |      t!        j$                  |      d'z  }d(d)|iz  }|j3                  |       |r_t!        j"                  d*fd+f      t!        j$                  |      t!        j$                  |      d,z  }d-d.|iz  }|j3                  |       t!        j4                  |d      i z  }t!        j&                  d/|j                  d"             d0z   d1|iz  }t)        t!        j*                  |            dx}x}x}x}x}x}x}x}x}}y# t        $ rG 	 ddlm} |j                  t        d
      }n%# t        $ r ddlm} |j                         }Y nw xY wY w xY w)2u   
        G-18 (LOW): pytesseract 설치 환경에서 영문 텍스트만 그린 PNG → 한글 비율 < 50% → passed=False.
        pytesseract 미설치 시 skip.
        r   u4   pytesseract not installed — skip korean_ratio testr  r$   r+   r   r   z4/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttfr^   r$  )r,   i|  zENGLISH ONLY TEXTr   r   )r,   i  zNO KOREAN HERE)r,   iD  zABCDEFGHIJ 12345r   r   rS   rz   Fr{   r}   r~   u7   텍스트 미감지 케이스도 passed=False여야 함r   r   Nu^   영문만 있는 이미지는 한글 비율 부족으로 passed=False여야 함. korean_ratio=korean_ratior   r   u   한글r   )zN%(py8)s
{%(py8)s = %(py4)s
{%(py4)s = %(py2)s.get
}(%(py6)s)
} is not %(py11)sr   )r   r   r   r  r(  z%(py13)spy13r&  )z%(py16)s in %(py19)s)r*  r+  z%(py21)spy21u*   reason에 '한글' 포함 필요. 실제: z
>assert %(py24)spy24)r   r  r   r&   r   r   r4   r   r   r   r   FONT_REGULARr   r   r   rB  r   r   r   r   r   r   r   r   r   r   r   )r   r6   r7   r   r   r   r   r   r   r   r   r   r-  r  r.  @py_assert9@py_assert15@py_assert18@py_assert17@py_format12@py_format14r4  @py_format22@py_format23@py_format25s                            r    (test_ocr_english_only_fails_korean_ratiozBTestLokiG2RegressionBlock.test_ocr_english_only_fails_korean_ratio@  s	   
 	M2hiiitY/~~c"		0%%%&\^`aD 			*1		M		*.YT	J		*0yt	L%c* ::&*c1VZZ@P5QUW5W(# u #u,  #u  I $  I (-    J      (# u #u,  #u   I $   I (-     &

> :;9VZZPXEYDZ\    6:: h :h' t 't3  F8DT DT8T   't  v     I   I   I '  I (  I 04   v DT  I 9A  I EU   v    =VZZ=Q<RS      7  	00) )),; 0) --/0		0s6   	O9 9	Q	P Q	 Q?Q	QQ	Q	N)r   r   r   r   r5  r;  rC  rS  r   r"   r    r   r     s    B
2
.[4)r"   r   )r   tuplereturnImage.Image)r6   rV  rs   r   rt   strrU  r   )4r   
__future__r   builtinsr   _pytest.assertion.rewrite	assertionrewriter   rC   syspathlibr   numpyr   r   r   r   r   pathinsertrW  rA  r   r   r	   r
   r   r   r   r   rI  r   r   r!   r(   r8   r=   rK   rQ   r\   re   rm   rp   rv   rx   r   r   r   r   r  r   r   r"   r    <module>rb     s"  & #    
     
 3tHIJ K   ?	D<	 +/   $( -
 '+  &*   *.  " *.  , 48   $(   '+   $( -
+
 +
dS
 S
t#
 #
T;
 ;
D
 
H!
 !
Pv vr"   